为何同方法内GC未触发?解析-Xcomp与拆方法的内存问题
Parallel GC下JDK8内存分配异常:同方法内GC未触发的原因解析
最近测试Parallel GC时碰到了一个有意思的现象:在同一个方法里连续分配大数组时,GC居然没正常触发,直接爆了OOM;但只要加个-Xcomp参数,或者把分配逻辑抽去单独方法,就能正常运行。先把测试场景和代码贴出来:
触发OOM的测试代码
public static void main(String[] args) { System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); String[] strings = new String[(40 * 1024 * 1024) / Integer.BYTES]; System.out.println(strings.length); System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); strings = new String[(40 * 1024 * 1024) / Integer.BYTES]; System.out.println(strings.length); System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); }
运行环境:Java 1.8.0_191,JVM参数:-Xmx64m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
现象:第二次分配数组时直接抛出OutOfMemoryError
两种解决方式
- 添加
-Xcomp参数后,程序正常运行,GC会主动回收旧数组内存; - 将分配逻辑抽离到单独方法,同样不会触发OOM:
public static void main(String[] args) { System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); allocate(); System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); allocate(); System.out.println(Runtime.getRuntime().freeMemory() / (1024 * 1024) + " free"); } private static void allocate() { String[] strings = new String[(40 * 1024 * 1024) / Integer.BYTES]; System.out.println(strings.length); }
问题1:为什么加-Xcomp参数能解决问题?
-Xcomp的作用是强制JVM启动时就把所有字节码编译成本地机器码,跳过默认的「解释执行+分层编译」模式。这里的核心原因和JIT编译的逃逸分析以及局部变量生命周期优化有关:
- 在解释执行模式下,JVM不会做激进的优化,局部变量
strings的生命周期会被延长到整个方法结束——哪怕你已经把它重新赋值给新数组,旧数组的引用还存在于局部变量表中,GC会认为它还可达,不会回收这部分内存。第二次分配时,内存直接不够用就爆OOM了。 - 当用
-Xcomp强制编译后,JIT编译器会做逃逸分析:它能识别到第一个数组在第二次分配前已经完全没有被引用,会主动缩短这个变量的生命周期,让旧数组提前变成垃圾。同时,编译后的内存分配逻辑会更及时地触发GC,在OOM发生前回收掉旧数组的内存,自然就能正常分配新数组了。
问题2:为什么把分配逻辑移到单独方法就能解决?
这同样和变量生命周期以及GC的可达性判断有关:
- 当分配逻辑在单独的
allocate()方法里时,每次调用完这个方法,方法的栈帧就会被弹出栈,栈帧里的局部变量strings也会被销毁——对应的数组对象瞬间就变成了不可达对象。不管是解释执行还是JIT编译,GC都能很快识别到这些垃圾并回收。 - 反观原始的同方法内分配,解释执行时JVM不会主动清除局部变量表中的旧引用,直到整个main方法结束才会释放。哪怕你重新赋值了变量,旧数组的内存还是被占用着,第二次分配时自然没有足够空间。另外,如果
allocate()被JIT编译,逃逸分析还会发现数组完全没逃出方法作用域,甚至可能做栈上分配优化(不过数组是对象,JDK8需要加-XX:+EliminateAllocations才会开启,但哪怕没开,方法结束后对象也会立即变成垃圾)。
内容的提问来源于stack exchange,提问作者Almas Abdrazak
相关产品推荐
相关产品推荐

