0 实战参考
1 现象及原因
堆外内存
java 8下是指除了Xmx设置的java堆(java 8以下版本还包括MaxPermSize设定的持久代大小)外,java进程使用的其他内存。主要包括:DirectByteBuffer分配的内存,JNI里分配的内存,线程栈分配占用的系统内存,jvm本身运行过程分配的内存,codeCache,java 8里还包括metaspace元数据空间。
- 大量使用堆外内存:java应用所占内存明显大于xmx指定的堆最大内存空间。如java应用占7G内存,指定堆最大内存为4G,那么很可能出现了堆外内存泄漏。
- 原因:比如存在不断加载class对象、主动分配堆外内存等场景。如jetty、faskJson使用或配置问题都出现过堆外内存泄漏
- GC停顿时间长:GC停顿时间达到S级别,比如超过1S。
- 原因:堆外内存不断增加,又没触发full gc的情况下,会使用swap虚拟内存来保证内存充足。但磁盘操作相比内存耗时长,使用大量swap内存会导致gc时间过长!
2 命令与工具
2.1 常用命令
- top:是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。
- jmap:查看JVM内存使用情况。
- jmap -heap pid 展示pid的整体堆信息。( 堆内存初始化配置、当前使用情况)
- jstat:命令查看jvm的GC情况
- 各分区使用情况:
- 各分区使用占比:
- 注意:jstat输出中的MC对应于元空间提交的大小,而不是容量。
2.2 堆外内存分析工具
2.2.1 NMT使用
【介绍】
- NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存,可以通过 jcmd 工具访问 NMT 数据。
- NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。
【使用】
- 启动命令: -XX:NativeMemoryTracking=[off | summary | detail]。
- 启用 NMT 会带来 5-10% 的性能损失。
【举例】
jcmd <pid> VM.native_memory summary 输出:
Total: reserved=664192KB, committed=253120KB <--- total memory tracked by Native Memory Tracking
- Java Heap (reserved=516096KB, committed=204800KB) <--- Java Heap
(mmap: reserved=516096KB, committed=204800KB)
- Class (reserved=6568KB, committed=4140KB) <--- class metadata
(classes #665) <--- number of loaded classes
(malloc=424KB, #1000) <--- malloc'd memory, #number of malloc
(mmap: reserved=6144KB, committed=3716KB)
- Thread (reserved=6868KB, committed=6868KB)
(thread #15) <--- number of threads
(stack: reserved=6780KB, committed=6780KB) <--- memory used by thread stacks
(malloc=27KB, #66)
(arena=61KB, #30) <--- resource and handle areas
- Code (reserved=102414KB, committed=6314KB)
(malloc=2574KB, #74316)
(mmap: reserved=99840KB, committed=3740KB)
- GC (reserved=26154KB, committed=24938KB)
(malloc=486KB, #110)
(mmap: reserved=25668KB, committed=24452KB)
- Compiler (reserved=106KB, committed=106KB)
(malloc=7KB, #90)
(arena=99KB, #3)
- Internal (reserved=586KB, committed=554KB)
(malloc=554KB, #1677)
(mmap: reserved=32KB, committed=0KB)
- Symbol (reserved=906KB, committed=906KB)
(malloc=514KB, #2736)
(arena=392KB, #1)
- Memory Tracking (reserved=3184KB, committed=3184KB)
(malloc=3184KB, #300)
- Pooled Free Chunks (reserved=1276KB, committed=1276KB)
(malloc=1276KB)
- Unknown (reserved=33KB, committed=33KB)
(arena=33KB, #1)
3 定位流程
3.1 排除堆内存泄漏
是否频繁出发full gc,内存泄漏对象会场景无法回收,从而导致频繁full gc
3.2 分析堆外内存
如果出现堆外内存泄漏,可能导致
- 堆外内存呈现线性增长的趋势
- 堆外内存占用空间多
3.3 定位具体原因
使用堆外内存分析工具,定位到具体原因。
- 监控到具体对象
- 监控到具体调用入口
4 配置项
- -Xmx:设置JVM最大可用内存
- -Xms:设置JVM初始内存
- -Xmn:新生代大小
- -Xss:设置每个线程的堆栈大小
- -XX:MetaspaceSize:设定触发FGC的阈值,实际使用内存按需分配;并不会一开始就分配设置大小的内存,其实从jstat -gc命令可以发现这一点。
- MetaspaceSize和MaxMetaspaceSize设置一样大
- MetaspaceSize值建议设置为应用稳定运行后1.2-1.5倍
- -XX:MaxMetaspaceSize:元空间最大可分配内存
- -XX:MaxDirectMemorySize:用于设置直接内存的最大值
G1 垃圾回收器参数
- -XX:MaxGCPauseMillis:用于设置目标停顿时间,G1 会尽力达成。
5 调优思路
堆外内存溢出可能场景包括
- 线程过多导致:
- 不断创建class类对象:
- JNI方法分配本地内存:
- 其他:
- xms与xmx建议设置一样:动态扩缩容会导致gc,内存足够的情况下可以保持一致。
- XX:MaxMetaspaceSize与XX:MaxDirectMemorySize设置一样:同理元空间初始值与最大值也建议保持一致