使用jmap生成堆转储后Java进程内存占用下降的原因
为什么使用jmap抓取堆转储后Java进程内存骤降,而平时GC不清理?
这问题我帮不少排查WildFly内存问题的开发者解答过,咱们从JVM的GC行为和WildFly的运行特性两方面拆解:
1. jmap默认会触发强制Full GC
当你执行jmap -dump:format=b,file=heapdump.bin时,默认会先触发一次Full GC,把所有可回收的对象(包括老年代里的无用对象、软引用/弱引用关联的对象、甚至元空间里的无用类)全部清理掉,这也是进程内存从X GB降到X-Y GB的核心原因。
而你的WildFly应用平时运行时,JVM的GC策略是尽量避免Full GC的:
- WildFly 9默认搭配的Java版本通常是Java 8,默认GC是Parallel Scavenge(年轻代)+ Parallel Old(老年代),只有当老年代内存使用率达到阈值(默认是92%左右)才会触发Full GC;
- 如果你们配置了CMS或G1这类低延迟收集器,JVM会优先用并发GC来清理内存,不会轻易触发Stop-the-world的Full GC——哪怕老年代里有一些可回收对象,只要内存还够,就会延迟清理以保证应用响应速度。
2. 内存累积未必是泄漏,可能是"可回收但未到回收时机"
很多时候看起来内存一直在涨,不一定是内存泄漏,而是这些对象属于**"可回收但JVM暂时不需要回收"**的范畴:
- 比如批量处理数据时产生的大对象,进入老年代后,Young GC管不到,而老年代还没满,所以不会触发Full GC;
- 软引用关联的对象,JVM只有在内存不足时才会回收,平时内存充足的话会保留这些对象;
- 还有一些框架(比如Hibernate、Spring)的缓存对象,只要缓存空间没满,也不会主动清理。
当jmap强制触发Full GC时,这些"待命"的可回收对象就被一次性清掉了,内存自然骤降。
3. Java进程内存不止堆内存
你看到的java.exe内存占用,除了堆内存,还包括直接内存、元空间、线程栈、Native内存等:
- Full GC不仅清理堆,还会触发元空间里无用类的卸载(如果开启了
-XX:+CMSClassUnloadingEnabled这类参数); - 部分直接内存的回收依赖GC触发,Full GC后这些直接内存也会被释放;
- 甚至一些Native库占用的内存,也可能在Full GC后被间接释放,这也是内存降幅大的原因之一。
排查建议
如果想解决内存累积的问题,可以做这些操作:
- 开启GC日志:在WildFly的启动参数里加上
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log,观察平时的GC频率、老年代使用率,看看是不是Full GC触发的阈值太高; - 分析堆转储:用VisualVM或Eclipse Memory Analyzer打开你抓取的堆转储,看看那些被清理掉的对象是什么类型,是不是有周期性的批量任务产生了大量临时对象;
- 调整GC参数:如果是老年代内存一直占满但没触发Full GC,可以适当调低老年代的触发阈值(比如CMS的
-XX:CMSInitiatingOccupancyFraction=70),让JVM更早开始清理老年代。
内容的提问来源于stack exchange,提问作者Anjan Baradwaj
相关产品推荐
相关产品推荐

