Java程序抛出OutOfMemoryError但堆转储显示大量空闲内存原因排查
问题分析与解决方案
你遇到的这个问题,根源大概率是G1垃圾收集器的Remembered Set(RS,记忆集合)内部结构——那些你看到的int[262136]数组,正是G1用来跟踪跨Region引用的核心数据结构,并非你的代码直接创建的。
为什么会出现这种情况?
Java 11和14默认使用G1收集器,它将堆划分为多个大小相等的Region。为了高效处理垃圾回收,每个Region都对应一个Remembered Set,用来记录其他Region对当前Region内对象的引用。这些RS的底层实现就是固定大小的int数组(你看到的262136长度,换算下来刚好是~1MB左右,和G1默认的Region大小(通常2MB)匹配)。
当你的堆接近-Xmx设置的8GB上限时,G1需要维护的RS数量会随着Region数量增加而剧增(8GB堆用2MB Region的话,就是4096个Region),所有RS的总内存占用刚好能达到4GB左右——这正好对应你堆转储中那部分“未引用”的内存。
至于为什么工具会把这些数组标记为“未引用对象”,是因为这些数组属于JVM内部的私有实现,MAT或VisualVM无法识别它们被G1收集器的核心对象持有引用,所以误判为可回收的垃圾,但实际上它们是存活的、不可回收的内部结构,正是它们占用的4GB内存加上你的应用数据的4GB,直接填满了8GB堆,引发了OutOfMemoryError。
解决方案
针对这个问题,你可以尝试以下几种方案:
- 调整G1的Region大小:通过
-XX:G1HeapRegionSize参数增大Region的尺寸(比如设为4MB或8MB),这样Region的数量会减少,RS的总内存占用也会随之降低。例如:java -Xmx8000m -XX:G1HeapRegionSize=4m YourMainClass - 切换到其他垃圾收集器:如果你的应用场景适合,可以改用Parallel GC(吞吐量优先),它没有G1这种RS结构,也就不会产生这些占用内存的int数组。启动参数添加:
java -Xmx8000m -XX:+UseParallelGC YourMainClass - 增大堆内存上限:如果资源允许,直接调高
-Xmx的值(比如-Xmx12000m),给G1的RS和你的应用数据都留出足够的空间。
内容的提问来源于stack exchange,提问作者fjalvingh
相关产品推荐
相关产品推荐

