今天分享的參數是 -XX:ParGCCardsPerStrideChunk
diagnostic(intx, ParGCCardsPerStrideChunk, 256, \
"The number of cards in each chunk of the parallel chunks used during card table scanning")
一個神奇的參數,看描述似乎還是比較迷糊,還是展開來說下。
發生young gc時,有一個特殊的GC Roots,那就是old gen中的對象,當old gen的對象A引用了young gen的對象B,那么對象B是不能被回收的。
所以要在old gen中找出所有和對象A類似的對象(引用了young gen對象),怎么找呢?最暴力的辦法就是遍歷所有的old gen對象,顯然這種方式效率很低下,在HotSpot實現中,提供了一種叫CardTable的數據結構,用來表示一塊內存區域,一般是512字節,如果這塊內存中有對象引用了young gen對象,那么就標識這個CardTable為Dirty的,這樣在內存掃描時,只需要掃描標識為Dirty的內存區域中的對象即可,避免了全old gen的掃描,大大提升了掃描效率。
在ParNew算法中,掃描old gen的CardTable由多個線程完成,其中ParGCCardsPerStrideChunk
參數就是每個線程處理的CardTable數量,默認是256個,意思每個線程每次處理大小為 256*512byte = 128K的StrideChunk,如果old gen大小4G,那么一共要處理 4G / 128K = 32K個StrideChunk,這么多的StrideChunk要分配給GC線程,假設有4個線程在并發執行,必然存在任務的調度和分配問題,影響到掃描效率。
如果把這個參數調大,那么每個線程每次處理的StrideChunk也相應變大,總的StrideChunk個數相應的減少,GC線程在不同的StrideChunk切換次數也會減少,這樣是不是可以提升一點性能呢?
那么應該調到多大呢?之前看過一篇LinkedIn的Engineering Blog,他們在不同的配置下經過測試之后得出了32768是最佳值的結論,后來很多的人覺得這個值很神奇,不假思索的也設置了這個值,但這個值真的適合你的應用么?這個非常值得懷疑。
之后Twitter的一批人也高了一個OpenJDK的測試,他們發現8192是一個合適的值,公說公有理,婆說婆有理,我們不用太較真,抱著學習的心態就行。
最后,我們看看這個參數的提交者怎么說,一個俄國人Alexey Ragozin,這是他的一篇關于該參數的文章,傳送門,他在發現CardTable掃描時的效率問題后,在7u40時patch了這個問題,在他的實驗中,對28G和14G分別,且該參數設置成4K,結果如下:
不管怎么說,在他的實驗中,GC性能確實提升不少,但也不能說明4K就是一個完美值,該值也只是非常符合他當前的實驗而已。
仔細想想,如果old gen中需要掃描的CardTable其實不多,如果盲目的增大該參數,也會得不償失,這時256未嘗不是一個合適的值,需要找到一個平衡點。
因為該參數是diagnostic類型的,所以要使修改的值生效,需要添加下面參數:
-XX:+UnlockDiagnosticVMOptions
-XX:ParGCCardsPerStrideChunk=4096
花了這么長時間來解釋這個參數,只是像說明一點,在JVM調優過程中,沒有一個參數的值完美的,只有經過不斷的調優過程,慢慢的摸索到適合自己應用的最佳參數范圍,除非應用對YGC的耗時特別敏感,不到萬不得已,不用優化該參數,256也適合大部分情況。但是隨著現在機器內存的擴大,適當的增大該參數值(4K),也是沒有問題的。
我是占小狼,如果覺得有收獲,歡迎關注。