Truman
Posted on November 19, 2024
启动命令
-Xms8G -Xmx8G -Xmn4G -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParallelGC
根据启动命令的配置和 Kubernetes 显示的内存占用,出现了 JVM 内存(堆和非堆内存)与实际进程内存占用不一致 的现象。
JVM 配置
堆内存
- 最小堆大小(-Xms):8GB
- 最大堆大小(-Xmx):8GB
- 年轻代大小(-Xmn):4GB (年轻代是堆的一部分)
元空间(Metaspace)
- 初始元空间大小(-XX:MetaspaceSize):256MB
- 最大元空间大小(-XX:MaxMetaspaceSize):256MB
垃圾回收器
- GC 类型:Parallel GC (吞吐量优先,但会占用更多内存来优化 GC 性能)
Kubernetes 监控显示
- 应用占用 9.4GB 内存,超出配置的 8GB 堆大小。
内存占用组成
JVM 应用的实际内存占用不仅包括堆内存,还包括以下部分:
1. 堆内存(Heap Memory)
- 定义为
-Xms
和-Xmx
的 8GB 堆内存。 - 组成部分:
-
年轻代:配置为
-Xmn=4G
。 - 老年代:存放从年轻代晋升的长生命周期对象。
-
年轻代:配置为
2. 非堆内存(Non-Heap Memory)
-
元空间(Metaspace):
配置了最大 256MB (
-XX:MaxMetaspaceSize=256MB
),但通常会稍微超过此值。 - 代码缓存区(Code Cache): 存储 JIT 编译后的代码,大小随 JIT 编译器的使用而增长。
-
线程栈内存(Thread Stack Memory):
- 每个线程分配的栈内存由
-Xss
配置(默认 1MB)。 - 线程数量较多时,线程栈内存可能占用大量内存。
- 每个线程分配的栈内存由
3. 堆外内存(Direct Memory)
- 使用
ByteBuffer.allocateDirect
或 NIO 时分配的直接内存。 - 默认情况下,直接内存大小与最大堆大小(
-Xmx
)相同,可通过-XX:MaxDirectMemorySize
限制。
4. GC 线程和内部内存
- GC 线程: Parallel GC 垃圾回收线程会占用本地内存和线程栈空间。
- 工作缓冲区: Parallel GC 会分配额外的工作缓冲区,用于垃圾回收操作,占用额外内存。
5. C 库或第三方库的内存
- JNI 调用的本地库: 通过 JNI 调用的库(如 Netty、OpenSSL 等)可能分配堆外内存。
- 这些内存由本地代码管理,不受 JVM 的直接控制。
可能原因分析
根据当前情况,以下是造成 Kubernetes 显示内存占用高于 JVM 配置的主要原因:
1. 线程栈内存消耗
-
默认线程栈大小:1MB(通过
-Xss
配置)。 -
影响:
- 如果应用使用大量线程(如处理高并发请求的线程池),线程栈内存会显著增加内存占用。
-
假设线程数量为 1000,线程栈内存占用约为:
1MB * 1000 = 1GB
2. 堆外内存(Direct Memory)
-
堆外内存的分配:
- 默认与最大堆大小一致(8GB)。
- 通常由 NIO 或框架(如 Netty)分配。
-
影响:
- 操作系统可能为堆外内存保留虚拟地址空间,即使实际使用未达到上限。
3. Parallel GC 的额外内存需求
-
GC 内存使用:
- Parallel GC 的多线程操作会分配额外内存用于内部数据结构(如标记、复制和整理)。
-
相关配置:
- GC 线程数与 CPU 核心数相关,通过
ParallelGCThreads
调整。
- GC 线程数与 CPU 核心数相关,通过
-
影响:
- 如果垃圾回收线程过多,内存占用会显著增加。
4. 元空间和代码缓存区增长
-
元空间的增长:
- 虽然配置了最大 256MB (
-XX:MaxMetaspaceSize=256MB
),但 JVM 会动态分配更多内存用于类加载器和其他用途。
- 虽然配置了最大 256MB (
-
代码缓存区:
- 用于存储 JIT 编译后的代码,随着运行时间增加可能增长。
-
影响:
- 元空间和代码缓存区的增长可能导致非堆内存膨胀,进一步提升内存占用。
排查方法
以下方法可以帮助确认内存使用的具体来源:
1. 使用 Native Memory Tracking (NMT)
功能
NMT 可以精确追踪 JVM 内存分配,帮助识别内存的分布和使用情况。
启用 NMT
在 JVM 启动参数中添加以下配置:
-XX:NativeMemoryTracking=summary
查看内存分布
进入容器并运行:
jcmd 1 VM.native_memory summary
输出示例:
sh-4.4# jcmd 1 VM.native_memory summary
1:
Native Memory Tracking:
(Omitting categories weighting less than 1KB)
Total: reserved=9488872KB, committed=9037604KB
malloc: 90692KB #530181
mmap: reserved=9398180KB, committed=8946912KB
- Java Heap (reserved=8388608KB, committed=8388608KB)
(mmap: reserved=8388608KB, committed=8388608KB)
- Class (reserved=216083KB, committed=19475KB)
(classes #24831)
( instance classes #23390, array classes #1441)
(malloc=3091KB #81714)
(mmap: reserved=212992KB, committed=16384KB)
( Metadata: )
( reserved=131072KB, committed=126208KB)
( used=125492KB)
( waste=716KB =0.57%)
( Class space:)
( reserved=212992KB, committed=16384KB)
( used=15382KB)
( waste=1002KB =6.12%)
- Thread (reserved=97958KB, committed=10654KB)
(thread #96)
(stack: reserved=97644KB, committed=10340KB)
(malloc=205KB #579)
(arena=109KB #188)
- Code (reserved=254760KB, committed=95700KB)
(malloc=7072KB #22671)
(mmap: reserved=247688KB, committed=88628KB)
- GC (reserved=310290KB, committed=310286KB)
(malloc=6542KB #99)
(mmap: reserved=303748KB, committed=303744KB)
- Compiler (reserved=821KB, committed=821KB)
(malloc=657KB #1513)
(arena=164KB #4)
- Internal (reserved=1608KB, committed=1608KB)
(malloc=1572KB #53569)
(mmap: reserved=36KB, committed=36KB)
- Other (reserved=16890KB, committed=16890KB)
(malloc=16890KB #52)
- Symbol (reserved=41301KB, committed=41301KB)
(malloc=36179KB #334650)
(arena=5122KB #1)
- Native Memory Tracking (reserved=8483KB, committed=8483KB)
(malloc=199KB #3578)
(tracking overhead=8284KB)
- Shared class space (reserved=16384KB, committed=12956KB, readonly=0KB)
(mmap: reserved=16384KB, committed=12956KB)
- Arena Chunk (reserved=692KB, committed=692KB)
(malloc=692KB #373)
- Module (reserved=252KB, committed=252KB)
(malloc=252KB #4553)
- Safepoint (reserved=8KB, committed=8KB)
(mmap: reserved=8KB, committed=8KB)
- Synchronization (reserved=2592KB, committed=2592KB)
(malloc=2592KB #25509)
- Serviceability (reserved=18KB, committed=18KB)
(malloc=18KB #36)
- Metaspace (reserved=132121KB, committed=127257KB)
(malloc=1049KB #1245)
(mmap: reserved=131072KB, committed=126208KB)
- String Deduplication (reserved=1KB, committed=1KB)
(malloc=1KB #8)
- Object Monitors (reserved=4KB, committed=4KB)
(malloc=4KB #19)
重点关注:
- Thread:线程栈内存。
- Direct:堆外内存。
2. 检查线程数量
进入 Pod 后,运行以下命令检查线程数:
ls /proc/<PID>/task | wc -l
输出示例
sh-4.4# ls /proc/1/task | wc -l
116
如果线程数量较多(数百甚至上千),需要检查线程池配置,避免过多线程占用栈内存。
3. 检查 Direct Memory
可以通过以下代码查看直接内存的使用情况:
System.out.println("Max Direct Memory: " + sun.misc.VM.maxDirectMemory() / (1024 * 1024) + " MB");
或者使用 jcmd 查看堆外内存分布。
4. 启用 GC 日志
在 JVM 启动参数中添加:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log
分析垃圾回收日志,确认是否有过多的垃圾回收线程或堆外内存清理未及时释放。
优化建议
1. 减少线程栈内存
设置更小的线程栈大小(例如 512KB):
-Xss512k
这可以显著减少线程栈内存占用。
2. 限制直接内存
设置直接内存的上限,例如 256MB:
-XX:MaxDirectMemorySize=256m
Posted on November 19, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.