下午休息的时候,小伙伴突然扔给我一段代码
public class GCTest {
public static void main(String[] args) {
List<GCTest> aa = new ArrayList<>();
for (; ; ) {
aa.add(new GCTest());
System.gc();
}
}
}
然后问到:为什么不加System.gc()这一行就会OOM,加上System.gc()就不再OOM了。
我问他:哪来的代码?
他说:是看公开课,一个资深讲师讲的,只给了结论说是会GC,所以不OOM,具体原因也没说清楚,所以来问问我怎么回事。
说实话,我当时就蒙了,为什么加上System.gc()就不OOM了呢?多年的“客服”(中间件开发小伙伴的自嘲)经验告诉我,不要相信任何人,先本地验证,再说结论。
先限制下堆大小,快速复现问题。-Xms10m -Xmx10m
果不其然啊,没有System.gc()的时候,很快就OOM了。加上System.gc()的时候,跑很久也没OOM。
但是完全不对啊,和我的认知有冲突啊,完全超出我的知识范围了。不行,再加快OOM速度,再验证。
public class GCTest {
byte[] bytes = new byte[4096];
public static void main(String[] args) {
List<GCTest> aa = new ArrayList<>();
for (; ; ) {
aa.add(new GCTest());
System.gc();
}
}
}
这回就对了,无论有没有System.gc(),都会很快OOM。
然后截图,发给小伙伴,告诉他:不信谣、不传谣。什么资深讲师,找他退钱。
我们从“资深讲师”的角度,来想一下,为什么他会得出加上System.gc()了,就不会OOM了,这种错误的结论呢?
第一个原因,我想讲师应该是要讲不可达对象的回收:
public class GCTest {
byte[] bytes = new byte[4096];
public static void main(String[] args) {
for (; ; ) {
test();
}
}
private static void test() {
List<GCTest> aa = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
aa.add(new GCTest());
}
}
}
当方法退出时,循环创建的1000个GCTest对象就变为了不可达对象,当进行GC时,可以被回收。
第二个原因,我想讲师可能是要讲System.gc()的作用,但是用错了例子。
System.gc()主要是做了哪些内容?这个搜一下,都是以下五条:
- system.gc其实是做一次full gc
- system.gc会暂停整个进程
- system.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
- system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些)
- system.gc最常见的场景是RMI/NIO下的堆外内存分配等
这也正好说明了,第一次试验加了System.gc()之后,不是没有OOM,而是因为连续的full gc,长时间的stop the world,导致OOM时间延后了而已,如果坚持跑下,依旧会发生OOM的。
并且即使调用了System.gc()也不一定会进行一次full gc,这和实际运行的虚拟机是有关系的,我们常用的hotspot虚拟机实际会根据设置的JVM参数来决定执行什么类型的GC。这部分内容需要看源码,再继续学习了。
综上,主要是想说明一个问题,有时候我们缺的就是对所谓权威的挑战,在技术上只是闻道有先后、术业有专攻而已。