如何通过堆文件定位unreachable_objects中byte[]对象的引用来源
我的Java应用频繁触发FullGC,执行jmap -dump:file=xxx获取堆文件后,用MAT分析未定位到根因,仅发现存在约1G的unreachable_objects对象,全部为byte[]类型,MAT无法查看其详细信息,需要定位生成这些垃圾对象的具体代码段,找到这些byte[]的引用来源。
当前JVM配置
-server -Xmx6g -Xms6g -XX:NewRatio=2 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=6 -XX:+ParallelRefProcEnabled -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/export/Logs/gc.log
参考截图
- 环境信息:

- MAT unreachable对象统计:

- MAT对象直方图:

首先明确原因:MAT默认会将无GC Root引用的不可达对象统一汇总到unreachable_objects条目下折叠展示,不会保留单个对象的引用链,所以你看不到详细信息。这些1G的byte[]是待回收的垃圾对象,高频生成大体积短生命周期字节数组是触发频繁FullGC的核心原因,按以下优先级排查即可:
1. 调整MAT配置解析现有堆dump
不需要重新dump,先修改MAT配置让它展开所有不可达对象:
- 打开MAT安装目录下的
MemoryAnalyzer.ini配置文件,新增一行配置:-Dmat.unreachable.objects.threshold=0 - 重启MAT重新加载堆快照,原来汇总的1G unreachable对象会拆分为单个
byte[]实例,右键选中实例选择Path to GC Roots->With all references,即可看到对象被持有的引用链,定位到生成它的代码类。 - 注意:如果你之前dump时加了
live参数(即执行的是jmap -dump:live,file=xxx),dump过程会主动触发FullGC清除所有不可达对象,快照里根本不会保留这些垃圾byte[],这种情况需要重新dump,dump命令不要加live参数。
2. 用分配采样工具直接定位代码栈(准确率最高,线上无影响)
短生命周期对象在dump时可能已经被回收,抓不到引用链,直接用低开销的分配采样工具追踪byte[]的分配点,不需要重启应用:
- JDK8u262及以上、JDK11+版本用JFR:
执行命令jcmd <你的应用PID> JFR.start name=alloc_trace settings=profile duration=60s filename=alloc.jfr,等待60秒采样结束后,用JMC打开生成的jfr文件,进入「对象分配」视图,按byte[]类型过滤,即可直接看到所有字节数组的分配代码栈、分配总大小、分配速率,工具对业务的性能影响低于5%,可安全在线上使用。 - 低版本JDK8用AsyncProfiler:
下载AsyncProfiler工具后执行./profiler.sh start -e alloc <PID>,采样30-60秒后执行./profiler.sh stop -f alloc_flamegraph.html <PID>,打开生成的分配火焰图,找到byte[]对应的最宽栈帧,就是生成这些大数组的代码位置,采样开销低于2%。
3. 配置GC前自动dump抓取现场
如果暂时无法使用采样工具,给JVM新增以下参数重启应用:
-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/export/Logs/fullgc_dump/
每次FullGC触发前JVM会自动生成堆快照,这个时间点垃圾对象还未被回收,引用链完整,拿到快照后直接按大对象排序,找到体积最大的byte[]查看引用链即可。
额外排查提示
你当前的JVM参数下,6G堆内存年轻代仅占2G,其中单个Survivor区大小只有200M左右,只要生成的byte[]大小超过200M,就会绕过年轻代回收直接晋升到老年代,这类短生命周期大对象会快速占满老年代触发FullGC,优先排查文件IO、网络包解析、序列化/反序列化相关逻辑,是否存在一次性读取大文件/大报文到内存生成大字节数组、没有做分片处理的逻辑。
内容的提问来源于stack exchange,提问作者Feng Kai

