一個小夥伴的問題

下午休息的時候,小夥伴突然扔給我一段代碼

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。這部分內容需要看源碼,再繼續學習了。

綜上,主要是想說明一個問題,有時候我們缺的就是對所謂權威的挑戰,在技術上只是聞道有先後、術業有專攻而已。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章