引言
如何结合使用 JVM Heap 堆和 Kubernetes 内存的 requests 和 limits 并远离麻烦。
在容器环境中运行 Java 应用程序需要了解两者 —— JVM 内存机制和 Kubernetes 内存管理。这两个环境一起工作会产生一个稳定的应用程序,但是,错误配置最多可能导致基础设施超支,最坏情况下可能会导致应用程序不稳定或崩溃。我们将首先仔细研究 JVM 内存的工作原理,然后我们将转向 Kubernetes,最后,我们将把这两个概念放在一起。
JVM 内存模型简介
JVM 内存管理是一种高度复杂的机制,多年来通过连续发布不断改进,是 JVM 平台的优势之一。对于本文,我们将只介绍对本主题有用的基础知识。在较高的层次上,JVM 内存由两个空间组成 —— Heap 和 Metaspace。
非 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 标志控制)。
~ 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