天天看點

JVM的記憶體結構、垃圾回收算法和垃圾收集器的詳解

運作時資料區(Run-Time Data Areas)

JVM的記憶體結構、垃圾回收算法和垃圾收集器的詳解
  • 本地方法區:方法區和堆一樣,也是被所有線程共享的記憶體區域。它存儲已被虛拟機加載的類資訊,常量,靜态變量,即時編譯後的代碼資料。
  • 虛拟機棧(JVM Stacks):每個Java虛拟機線程都有一個私有Java虛拟機棧,與線程同時建立。每個方法執行的同時都會建立一個棧,用于存儲局部變量表,操作數棧,動态連結,方法出口等資訊,每一個方法從調用到直至完成的過程,對應着一個棧在虛拟機中入棧到出棧的過程。
  • 本地方法棧:與虛拟機棧發揮的作用類似,他們之間的差別是:虛拟機棧是為虛拟機執行Java方法提供服務;本地方法棧是為虛拟機執行native方法服務。
  • 堆(Heap ):堆是所有線程之間共享的記憶體區域。堆是運作時資料區,從中配置設定所有類執行個體和數組的記憶體。堆是在虛拟機啟動時建立的。
  • 程式計數器(PC Register):JVM支援多線程同時執行,每個線程都有自己的PC Register,線程正在執行的方法叫目前方法。如果該方法不是 native方法,則PC Register包含目前正在執行的Java虛拟機指令的位址。如果線程目前正在執行該方法是native方法,則Java虛拟機PC Register的值未定義。
  • 運作時常量池:是方法區的一部分。Class檔案除了版本,字段,方法,接口等描述資訊外,還有一項資訊是常量池,用于存放編譯期生成的字面量和符号的引用,這部分内容将在類加載後進入方法區的運作時常量池存放。

Java的記憶體結構

JVM的記憶體結構、垃圾回收算法和垃圾收集器的詳解

常用JVM配置參數

  • -Xms jvm初始的堆記憶體大小,預設為實體記憶體的1/64,等價于 -XX:InitialHeapSize=
  • -Xmx jvm最大的堆記憶體大小,預設為實體記憶體的1/4,等價于 -XX:MaxHeapSize=
  • -Xss 設定單個線程棧的大小,預設512k-1024k,等價于 -XX:ThreadStackSize
  • -Xmn 設定新生代的大小,預設為堆空間的1/3
  • -XX:MetaSpaceSize:設定元空間的大小,元空間本質和永久代類似,都是對JVM規範中本地方法區的實作。不過元空間與永久代最大的差別在于:元空間并不在虛拟機中,而是使用本地記憶體,是以預設情況下,元空間的大小僅受實體記憶體大小的限制。
  • -XX:SurvivorRatio:設定新生代中eden(survivorFrom)和S0/S1(survivorTo)的比例

    預設-XX:SurvivorRatio=8,Eden:S0:S1=8:0:0

    SurvivorRatio的值就是預設eden的比例占比,S0和S1占比相同

  • -XX:NewRatio:配置新生代和老年代在堆中空間大小的比例

    預設-XX:NewRatio=2,新生代占1,老年代占2,新生代占整個堆記憶體的1/3

    假如-XX:NewRatio=4,新生代占1,老年代占4,新生代占整個堆記憶體的1/5

    NewRatio就是設定老年代的占比值,剩下的1給新生代

  • -XX:MaxTunuringThreshold 設定垃圾的最大年齡,java8預設為15,如果設定值的話必須在0-15如果設定為0,則年輕代不經過Survivor區,直接進入年老代,對于老年代比較多的應用,可以提高效率如果設定的的比較大,則年輕代會在Survivor區進行多次複制,這樣增加 對象在年輕代的存活時間,增加在年輕代即被回收的機率。
  • -XX:+PrintGCDetails 列印GC詳細資訊
  • -XX:+PrintGCTimeStamps 列印GC日志的發生時間
  • -XX:+PrintGCDateStamps 列印GC日志的發生的日期
  • -XX:+PrintHeapAtGC 每次GC前後列印堆資訊
  • -XLoggc:/logs/gc.log 将GC日志以檔案形式輸出
  • -XX:+DisableExplicitGC 禁止手動GC:System.gc();
  • -XX:+HeapDumpOnOutOfMemoryError 當JVM發生OOM時,自動生成DUMP檔案
  • -XX:HeapDumpPath=${目錄} 生成DUMP檔案的路徑,也可以指定檔案名稱,例如:

    -XX:HeapDumpPath={目錄}/java_heapdump.hprof。如果不指定檔案名,預設為:java_pid_date_time_heapDump.hprof

垃圾回收算法

1. 什麼是垃圾?

簡單來說就是記憶體中不可能再被使用到的對象空間就是垃圾。

2 如何确定一個對象是垃圾?可以回收

  • 引用計數法:如果一個對象的引用為0,則可以回收。但是如果兩個對象互相引用,則這兩個對象永遠不可回收,是以現在基本不使用該方法判斷。
  • 枚舉根節點,可達性分析,Java虛拟機采用的算法。基本思路就是通過一系列名為“GC Roots”的對象作為起始點,開始向下搜尋,如果一個對象到GC Roots沒有任何引用鍊相連時,則說明此對象不可用。也即,給一個集合的引用為根出發,通過引用關系周遊圖,能被周遊到的就判定為存活,沒有被周遊到的自然判定為死亡。

Java中可以作為GC Roots對象有:

  • 虛拟機棧(棧幀中的局部變量區,也叫局部變量表)中引用的對象
  • 方法區中類靜态屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中Native方法引用的對象。
  • 線程

3. 常用的垃圾回收算法

複制算法

java堆從GC角度還可以細分為:新生代(Eden區,From Survivor區和To Survivor區,占用1/3的堆空間)和老年代(占據2/3的堆空間)。

Minitor GC的過程(複制-清空-互換)

Eden、SurvivorFrom複制到SurvivorTo,年齡+1;

  • 首先,當Eden區滿的時候,第一次觸發GC,把還活着的對象拷貝到SuvivorFrom區,當Eden區滿再次觸發GC的時候會掃描Eden區和SurvivorFrom區,對這兩個區域進行垃圾回收,經過這次回收還活着的對象,則直接複制到SuvivorTo區域(如果對象的年齡達到了老年代,則指派到老年代),同時把這些對象的年齡+1;
  • 清空Eden和SurvivorFrom中的對象,也即複制之後交換,誰空誰是To;
  • SurvivorFrom和SurvivorTo互換
  • 最後,SurvivorFrom和SurvivorTo互換,原SurvivorTo成為下一次GC時的SurvivorFrom區,部分對象會在From區域和To區域複制來複制去,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數預設是15)
  • 最終如果還是存活,就存入老年代。
    優點:整體複制,沒有産生記憶體碎片。
      缺點:浪費空間,大對象複制會耗時。
               
标記清除

先标記出要回收的對象,再統一回收這些要清除的對象。

優點:沒有大面積的複制,節約記憶體空間
  缺點:産生記憶體碎片。
           
标記整理

标記要回收的對象,再次掃描,并往一端滑動存活的對象。

優點:沒有記憶體碎片,可以使用bump.
  缺點:移動存活對象的成本,耗時間。
           

主流的三種算法各有優缺點,綜合來看,使用分代收集,複制算法用于年輕代,标記清除,标記整理用于老年代。

GC垃圾回收算法和垃圾收集器的關系?

GC算法(引用計數,複制,标記整理,标記清除)是記憶體垃圾回收的方法論。垃圾收集器就是GC算法的落地實作。

因為目前為止還沒有出現完美的垃圾回收器出現,更加沒有萬能的收集器,隻有針對具體的應用最合适和垃圾回收器,進行分代收集。

4種主要的垃圾收集器
  • serial 串行垃圾回收器:他為單線程環境設計且隻使用一個線程進行垃圾回收,會暫停所有的使用者線程是以不适合伺服器環境。
  • paraller 并行垃圾回收器:多個垃圾收集線程并行工作,此時使用者的線程是暫停的,适用于科學計算,大資料處理首台處理等弱互動的場景。
  • CMS 并發垃圾回收器:使用者線程和垃圾回收線程同時執行(不一定是并行,有可能交替進行),不需要停頓使用者線程網際網路公司多使用他,對于響應有時間限制的場景。
  • G1垃圾回收器:将堆記憶體分割為不同的區域,然後并發的對其進行垃圾回收。
如何選用合适的垃圾收集器?

組合的選擇:

1、單核的CPU或者小記憶體,單機程式

-XX:UseSerialGC

2、多CPU,需要最大吞吐量,如背景計算型應用

-XX:UseParallelGC 或者-XX:UseParallelOldGC

3、多CPU,追求最小停頓時間,最快速響應,如網際網路應用

-XX:+UserConcMarkSweepGC

-XX:+ParNewGC

參考連結:JVM官方規範

繼續閱讀