加入笨神(公眾號:你假笨
)帶頭創建的JVMPocket
群已經有一段時間了,有幸得到笨神指導一二,學到了不少東西,也糾正了自己對JVM一些誤解,還能給群里一些朋友提出的問題給出指引,榮幸榮幸;JVM博大精深,網上對它的解釋和認知魚龍混雜,如果沒有深厚的功底很難辨別真假;不用擔心,每個行業總有一些大牛是讓我們凡人膜拜的,JVM界公認的大牛就是rednaxelaFX
了,圈子人稱R大
,且這么評價他:
- R大說的是對的;
- R大說的是對的;
- R大說的是對的;
R大確實牛逼,先show一下他工作履歷:淘寶JVM組,美國OracleJVM組,Azul-ZingVM項目組;R大不僅牛逼,還非常謙虛活躍,在知乎上@他的問題,基本上都能得到他非常非常非常詳盡的解答,對JVM有興趣的朋友,強烈推薦關注他的知乎;另外,一些關注JVM的朋友基本上都看了周志明的<<深入理解Java虛擬機>>,R大對這本書一些有問題的地方也給出了更正,有興趣的朋友可以看看,請點擊傳送門;
讓JVM按照預期GC
說起在JVMPocket里事情太多太多了,今天只說一下入群第一天笨神給我們出的一道題目:寫出讓JVM先3次YoungGC再1次CMS GC
的代碼;JVM比較復雜,很少有人能深入了解它,對絕大部分程序員來說JVM都是黑盒子;那么對我們這些不是專門從事JVM工作的程序員來說,了解它的第一步就是:知道它大概怎么運行,讓它按照自己的方式運行;所以笨神的這道題目很有價值,下面給出我的答案,源碼如下:
public class CmsGcTest {
private static final int _1M = 1*1024*1024;
private static final int _8M = 8*1024*1024;
public static void main(String[] args) {
// 預期YoungGC的次數
int ygcTime = 3;
for (int i=0; i<ygcTime; i++){
// 由于Eden區設置為8M, 所以分配8個1M就會導致一次YoungGC
for(int j=0; j<8; j++){
byte[] tmp = new byte[_1M];
}
}
for(int j=0; j<3; j++) {
// 對象超過了Eden區, 所以直接在Old區分配;
byte[] tmp = new byte[_8M];
}
try {
// sleep一段時間是為了讓CMS GC線程能夠有足夠的時間檢測到Old區達到了觸發CMS GC的條件,CMS GC線程默認2s掃描一次,可以通過參數CMSWaitDuration配置,例如-XX:CMSWaitDuration=3000
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序配套的JVM參數:
-verbose:gc -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xmx40m -Xms40m -Xmn10m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly
說明:這樣配置后,Eden區8M,S0/S1區各1M,old區30M,且當Old區占用60%就達到觸發CMS GC的條件;示例代碼中3次分配總計3*8M=24M>30*60%,達到了觸發CMS GC的條件;
一點點瑕疵
這段代碼依然有一點點遺憾,為了達到觸發CMS GC的條件,每次分配的對象不能小于Eden區的大小(在這段代碼中每次分配8M),如果要求Old區的大小在滿足不大于Eden區的大小同樣能達到需求,怎么辦?
更完美的方案
這里利用一個JVM參數PretenureSizeThreshold實現更完美的方案;用法:-XX:PretenureSizeThreshold=2M,含義是:當分配的對象超過設定值時不在Eden區分配,直接在Old區分配;
public class CmsGcTest {
private static final int _1M = 1*1024*1024;
private static final int _2M = 2*1024*1024;
public static void main(String[] args) {
ygc(3);
cmsGc(1);
// 在這里想怎么觸發GC就怎么調用ygc()和cmsGc()兩個方法
}
/**
* @param n 預期發生n次young gc
*/
private static void ygc(int n){
for (int i=0; i<n; i++){
// 由于Eden區設置為8M, 所以分配8個1M就會導致一次YoungGC
for(int j=0; j<8; j++){
byte[] tmp = new byte[_1M];
}
}
}
/**
* @param n 預期發生n次CMS gc
*/
private static void cmsGc(int n){
for (int i=0; i<n; i++){
for(int j=0; j<3; j++) {
// 由于設置了-XX:PretenureSizeThreshold=2M, 所以分配的2M對象不會在Eden區分配而是直接在Old區分配
byte[] tmp = new byte[_2M];
}
try {
// sleep10秒是為了讓CMS GC線程能夠有足夠的時間檢測到Old區達到了觸發CMS GC的條件并完成CMS GC, CMS GC線程默認2s掃描一次,可以通過參數CMSWaitDuration配置,例如-XX:CMSWaitDuration=3000
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
程序配套的JVM參數:
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xmx20m -Xms20m -Xmn10m -XX:PretenureSizeThreshold=2M -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly
說明:這樣配置后,Eden區8M,S0/S1區各1M,old區10M,且當Old區占用60%就達到觸發CMS GC的條件;