1. 程式人生 > >對《假笨說-又抓了一個導致頻繁GC的鬼--陣列動態擴容》實踐

對《假笨說-又抓了一個導致頻繁GC的鬼--陣列動態擴容》實踐

對《假笨說-又抓了一個導致頻繁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時間