對《假笨說-又抓了一個導致頻繁GC的鬼--陣列動態擴容》實踐
阿新 • • 發佈:2019-01-23
對《假笨說-又抓了一個導致頻繁GC的鬼–陣列動態擴容》實踐
1、本文根據此文章進行操作,學習jvm相關的知識:假笨說-又抓了一個導致頻繁GC的鬼–陣列動態擴容
2、基於jdk1.7實踐
1、用到的jvm引數:
- -Xmx550M -Xms550M -Xmn200M heap堆最大550m,初始heap大小為550m,最大新生代大小為200m;
- UseConcMarkSweepGC :This flag is needed to activate the CMS Collector in the first place. By default, HotSpot uses the Throughput Collector instead.
- -XX:+UseCMSInitiatingOccupancyOnly :不基於垃圾收集器收集的狀況來選擇使用收集器,而是直接使用cms收集器
- -XX:CMSInitiatingOccupancyFraction=80:當老年代的記憶體使用站80%時,觸發老年代回收;
- -XX:+PrintGC 列印gc日誌
- -XX:+PrintGCDetails 列印gc詳細日誌
- -XX:+PrintGCTimeStamps :gc的時刻
- -XX:+PrintGCApplicationStoppedTime :列印stop-the-word消耗的時間
- -XX:+CMSScavengeBeforeRemark :
2、 程式
/**
*-Xmx550M -Xms550M -Xmn200M
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+CMSScavengeBeforeRemark
* @author Rail
*/
public class Main {
public static void main(String[] args){
System.out.println("hi");
allocateMemory();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void allocateMemory() {
List<byte[]> list = new ArrayList<byte[]>();
int size = 480 * 1024 * 1024;
System.out.println(size);
int len = size / (20 * 1024);
System.out.println("len = "+len);
for(int i = 0; i < len; i++){
byte[] bytes = new byte[20*1024];
list.add(bytes);
/*if(i == 23760){
System.out.println("waiting");
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
if(i > 15740){
System.out.println("waiting 1s");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i+" "+20*(i + 1)+"kb");
}
}
}
3、記憶體分配過程
- 每次在新生代分配20kb的記憶體,約8000次(160mb)後,觸發parnew,因為空閒不能容納160mb,觸發老年代的分配擔保,從而新生代記憶體為20mb,老年代為140mb;
- 繼續分配20kb,分配16000次,新生代由20mb變為180mb
- 觸發parnew,空閒的sur不能容納180mb,觸發老年代分配擔保,從而新生代由180mb變為20mb,老年代為140mb+160mb=300mb;
- 老年代300mb觸發cms,因為分配的記憶體在此時都是不可回收的,cms雖然執行,但是不能縮小老年代的實際使用大小。
繼續分配20kb,在24000次,新生代由20mb達到180mb,
按照上面的邏輯是:空閒的sur不能容納180mb,觸發老年代分配擔保,因為老年代已經300mb了,最大是350mb,只能分配擔保50mb,新生代由180mb減小至150mb左右,然後後續的物件分配在新生代中分配,由150mb開始,從下圖列印的資訊看,這樣解釋也行得通:
但是在gc日誌中,是老年代由300mb變為350mb,然後新生代才由180mb變為110mb的。上一種說法就不成立了,那cms是可以主動的縮小新生代的大小嗎?這是我未解決的疑問。
4、jstat -gc pid 屬性說明
- S0C :survivor0的最大記憶體
- S1C :survivor1的最大記憶體
- S0U :survivor0的已用記憶體
- S1U :survivor1的已用記憶體
- EC :Eden區的最大記憶體
- EU:Eden區的已用記憶體
- OC:老年代的可用記憶體
- OU:老年代的已用記憶體
- MC:Metaspace(元空間)最大記憶體
- MU :Metaspace(元空間)已用記憶體
- CCSC:壓縮類空間最大記憶體
- CCSU:壓縮類空間已用記憶體
- YGC:年輕的gc次數
- YGCT:年輕代gc時間
- FGC:full gc次數
- FGCT:full gc時間
- GCT:總gc時間