Java 应用程序在 Kubernetes 上棘手的内存管理(上)

简介: Java 应用程序在 Kubernetes 上棘手的内存管理

引言

如何结合使用 JVM Heap 堆和 Kubernetes 内存的 requests 和 limits 并远离麻烦。

在容器环境中运行 Java 应用程序需要了解两者 —— JVM 内存机制和 Kubernetes 内存管理。这两个环境一起工作会产生一个稳定的应用程序,但是,错误配置最多可能导致基础设施超支,最坏情况下可能会导致应用程序不稳定或崩溃。我们将首先仔细研究 JVM 内存的工作原理,然后我们将转向 Kubernetes,最后,我们将把这两个概念放在一起。

JVM 内存模型简介

JVM 内存管理是一种高度复杂的机制,多年来通过连续发布不断改进,是 JVM 平台的优势之一。对于本文,我们将只介绍对本主题有用的基础知识。在较高的层次上,JVM 内存由两个空间组成 —— Heap 和 Metaspace。

JVM 内存模型

非 Heap 内存

JVM 使用许多内存区域。最值得注意的是 Metaspace。Metaspace 有几个功能。它主要用作方法区,其中存储应用程序的类结构和方法定义,包括标准库。内存池和常量池用于不可变对象,例如字符串,以及类常量。堆栈区域是用于线程执行的后进先出结构,存储原语和对传递给函数的对象的引用。根据 JVM 实现和版本,此空间用途的一些细节可能会有所不同。

我喜欢将 Metaspace 空间视为一个管理区域。这个空间的大小可以从几 MB 到几百 MB 不等,具体取决于代码库及其依赖项的大小,并且在应用程序的整个生命周期中几乎保持不变。默认情况下,此空间未绑定并会根据应用程序需要进行扩展。

Metaspace 是在 Java 8 中引入的,取代了 Permanent Generation,后者存在垃圾回收问题。

其他一些值得一提的非堆内存区域是代码缓存、线程、垃圾回收。更多关于非堆内存参考这里。

Heap 堆内存

如果 Metaspace 是管理空间,那么 Heap 就是操作空间。这里存放着所有的实例对象,并且垃圾回收机制在这里最为活跃。该内存的大小因应用程序而异,取决于工作负载的大小 —— 应用程序需要满足单个请求和流量特征所需的内存。大型应用程序通常具有以GB为单位的堆大小。

我们将使用一个示例应用程序用于探索内存机制。源代码在此处。

这个演示应用程序模拟了一个真实世界的场景,在该场景中,为传入请求提供服务的系统会在堆上累积对象,并在请求完成后成为垃圾回收的候选对象。该程序的核心是一个无限循环,通过将大型对象添加到列表并定期清除列表来创建堆上的大型对象。

val list = mutableListOf<ByteArray>()
generateSequence(0) { it + 1 }.forEach {
    if (it % (HEAP_TO_FILL / INCREMENTS_IN_MB) == 0) list.clear()
    list.add(ByteArray(INCREMENTS_IN_MB * BYTES_TO_MB))
}

以下是应用程序的输出。在预设间隔(本例中为350MB堆大小)内,状态会被清除。重要的是要理解,清除状态并不会清空堆 - 这是垃圾收集器内部实现的决定何时将对象从内存中驱逐出去。让我们使用几个堆设置来运行此应用程序,以查看它们对JVM行为的影响。

首先,我们将使用 4 GB 的最大堆大小(由 -Xmx 标志控制)。以下是应用程序的输出。在预设间隔(本例中为350MB堆大小)内,状态会被清除。重要的是要理解,清除状态并不会清空堆 - 这是垃圾收集器内部实现的决定何时将对象从内存中驱逐出去。让我们使用几个堆设置来运行此应用程序,以查看它们对JVM行为的影响。

首先,我们将使用 4 GB 的最大堆大小(由 -Xmx 标志控制)。

~ java -jar -Xmx4G app/build/libs/app.jar
INFO           Used          Free            Total
INFO       14.00 MB      36.00 MB       50.00 MB
INFO       66.00 MB      16.00 MB       82.00 MB
INFO      118.00 MB     436.00 MB      554.00 MB
INFO      171.00 MB     383.00 MB      554.00 MB
INFO      223.00 MB     331.00 MB      554.00 MB
INFO      274.00 MB     280.00 MB      554.00 MB
INFO      326.00 MB     228.00 MB      554.00 MB
INFO  State cleared at ~ 350 MB.
INFO           Used          Free            Total
INFO      378.00 MB     176.00 MB      554.00 MB
INFO      430.00 MB     208.00 MB      638.00 MB
INFO      482.00 MB     156.00 MB      638.00 MB
INFO      534.00 MB     104.00 MB      638.00 MB
INFO      586.00 MB      52.00 MB      638.00 MB
INFO      638.00 MB      16.00 MB      654.00 MB
INFO      690.00 MB      16.00 MB      706.00 MB
INFO  State cleared at ~ 350 MB.
INFO           Used          Free            Total
INFO      742.00 MB      16.00 MB      758.00 MB
INFO      794.00 MB      16.00 MB      810.00 MB
INFO      846.00 MB      16.00 MB      862.00 MB
INFO      899.00 MB      15.00 MB      914.00 MB
INFO      951.00 MB      15.00 MB      966.00 MB
INFO     1003.00 MB      15.00 MB     1018.00 MB
INFO     1055.00 MB      15.00 MB     1070.00 MB
...
...

有趣的是,尽管状态已被清除并准备好进行垃圾回收,但可以看到使用的内存(第一列)仍在增长。为什么会这样呢?由于堆有足够的空间可以扩展,JVM 延迟了通常需要大量 CPU 资源的垃圾回收,并优化为服务主线程。让我们看看不同堆大小如何影响此行为。

~ java -jar -Xmx380M app/build/libs/app.jar
INFO           Used          Free            Total
INFO       19.00 MB     357.00 MB      376.00 MB
INFO       70.00 MB     306.00 MB      376.00 MB
INFO      121.00 MB     255.00 MB      376.00 MB
INFO      172.00 MB     204.00 MB      376.00 MB
INFO      208.00 MB     168.00 MB      376.00 MB
INFO      259.00 MB     117.00 MB      376.00 MB
INFO      310.00 MB      66.00 MB      376.00 MB
INFO  State cleared at ~ 350 MB.
INFO           Used          Free            Total
INFO       55.00 MB     321.00 MB      376.00 MB
INFO      106.00 MB     270.00 MB      376.00 MB
INFO      157.00 MB     219.00 MB      376.00 MB
INFO      208.00 MB     168.00 MB      376.00 MB
INFO      259.00 MB     117.00 MB      376.00 MB
INFO      310.00 MB      66.00 MB      376.00 MB
INFO      361.00 MB      15.00 MB      376.00 MB
INFO  State cleared at ~ 350 MB.
INFO           Used          Free            Total
INFO       55.00 MB     321.00 MB      376.00 MB
INFO      106.00 MB     270.00 MB      376.00 MB
INFO      157.00 MB     219.00 MB      376.00 MB
INFO      208.00 MB     168.00 MB      376.00 MB
INFO      259.00 MB     117.00 MB      376.00 MB
INFO      310.00 MB      66.00 MB      376.00 MB
INFO      361.00 MB      15.00 MB      376.00 MB
INFO  State cleared at ~ 350 MB.
INFO           Used          Free            Total
INFO       55.00 MB     321.00 MB      376.00 MB
INFO      106.00 MB     270.00 MB      376.00 MB
INFO      157.00 MB     219.00 MB      376.00 MB
INFO      208.00 MB     168.00 MB      376.00 MB
...
...

在这种情况下,我们分配了刚好足够的堆大小(380 MB)来处理请求。我们可以看到,在这些限制条件下,GC立即启动以避免可怕的内存不足错误。这是 JVM 的承诺 - 它将始终在由于内存不足而失败之前尝试进行垃圾回收。为了完整起见,让我们看一下它的实际效果:

~ java -jar -Xmx150M app/build/libs/app.jar
INFO           Used          Free            Total
INFO       19.00 MB     133.00 MB      152.00 MB
INFO       70.00 MB      82.00 MB      152.00 MB
INFO      106.00 MB      46.00 MB      152.00 MB
Exception in thread "main"
...
...
Caused by: java.lang.OutOfMemoryError: Java heap space
 at com.dansiwiec.HeapDestroyerKt.blowHeap(HeapDestroyer.kt:28)
 at com.dansiwiec.HeapDestroyerKt.main(HeapDestroyer.kt:18)
 ... 8 more

对于 150 MB 的最大堆大小,进程无法处理 350MB 的工作负载,并且在堆被填满时失败,但在垃圾收集器尝试挽救这种情况之前不会失败。

Java Out Of Memory

我们也来看看 Metaspace 的大小。为此,我们将使用 jstat(为简洁起见省略了输出)

~ jstat -gc 35118
MU
4731.0

输出表明 Metaspace 利用率约为 5 MB。记住 Metaspace 负责存储类定义,作为实验,让我们将流行的 Spring Boot 框架添加到我们的应用程序中。

~ jstat -gc 34643
MU
28198.6

Metaspace 跃升至近 30 MB,因为类加载器占用的空间要大得多。对于较大的应用程序,此空间占用超过 100 MB 的情况并不罕见。接下来让我们进入 Kubernetes 领域。


Kubernetes 内存管理

Kubernetes 内存控制在操作系统级别运行,与管理分配给它的内存的 JVM 形成对比。K8s 内存管理机制的目标是确保工作负载被调度到资源充足的节点上,并将它们保持在一定的限制范围内。

Kubernetes Cluster 示例

在定义工作负载时,用户有两个参数可以操作 — requestslimits。这些是在容器级别定义的,但是,为了简单起见,我们将根据 pod 参数来考虑它,这些参数只是容器设置的总和。

当请求 pod 时,kube-scheduler(控制平面的一个组件)查看资源请求并选择一个具有足够资源的节点来容纳 pod。一旦调度,允许 pod 超过其内存requests(只要节点有空闲内存)但禁止超过其limits

Kubelet(节点上的容器运行时)监视 pod 的内存利用率,如果超过内存限制,它将重新启动 pod 或在节点资源不足时将其完全从节点中逐出(有关更多详细信息,请参阅有关此主题的官方文档。这会导致臭名昭著的 OOMKilled(内存不足)的 pod 状态。

当 pod 保持在其限制范围内,但超出了节点的可用内存时,会出现一个有趣的场景。这是可能的,因为调度程序会查看 pod 的请求(而不是限制)以将其调度到节点上。在这种情况下,kubelet 会执行一个称为节点压力驱逐的过程。简而言之,这意味着 pod 正在终止,以便回收节点上的资源。根据节点上的资源状况有多糟糕,驱逐可能是软的(允许 pod 优雅地终止)或硬的。此场景如下图所示。

Pod 驱逐场景

关于驱逐的内部运作,肯定还有很多东西需要了解。有关此复杂过程的更多信息,请点击此处。对于这个故事,我们就此打住,现在看看这两种机制 —— JVM 内存管理和 Kubernetes 是如何协同工作的。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
2月前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
188 77
|
2月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
128 62
|
2月前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
73 31
|
23天前
|
人工智能 Kubernetes 安全
赋能加速AI应用交付,F5 BIG-IP Next for Kubernetes方案解读
赋能加速AI应用交付,F5 BIG-IP Next for Kubernetes方案解读
59 13
|
22天前
|
存储 Kubernetes 关系型数据库
阿里云ACK备份中心,K8s集群业务应用数据的一站式灾备方案
本文源自2024云栖大会苏雅诗的演讲,探讨了K8s集群业务为何需要灾备及其重要性。文中强调了集群与业务高可用配置对稳定性的重要性,并指出人为误操作等风险,建议实施周期性和特定情况下的灾备措施。针对容器化业务,提出了灾备的新特性与需求,包括工作负载为核心、云资源信息的备份,以及有状态应用的数据保护。介绍了ACK推出的备份中心解决方案,支持命名空间、标签、资源类型等维度的备份,并具备存储卷数据保护功能,能够满足GitOps流程企业的特定需求。此外,还详细描述了备份中心的使用流程、控制台展示、灾备难点及解决方案等内容,展示了备份中心如何有效应对K8s集群资源和存储卷数据的灾备挑战。
|
29天前
|
开发框架 .NET PHP
网站应用项目如何选择阿里云服务器实例规格+内存+CPU+带宽+操作系统等配置
对于使用阿里云服务器的搭建网站的用户来说,面对众多可选的实例规格和配置选项,我们应该如何做出最佳选择,以最大化业务效益并控制成本,成为大家比较关注的问题,如果实例、内存、CPU、带宽等配置选择不合适,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文将详细解析企业在搭建网站应用项目时选购阿里云服务器应考虑的一些因素,以供参考。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
65 1
|
2月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
50 3
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
2月前
|
存储 运维 Kubernetes
K8s业务迁移最佳实践: 灵活管理资源备份与调整策略,实现高效简便的应用恢复
在当今快速变化的云原生领域,Kubernetes(K8s)集群的运维面临着诸多挑战,其中灾备与业务迁移尤为关键。ACK备份中心支持丰富的资源调整策略,在数据恢复阶段即可自动适配目标集群环境,确保业务无缝重启。