1. 程式人生 > >JVM 第3章垃圾收集器與記憶體分配策略

JVM 第3章垃圾收集器與記憶體分配策略

1 概述

程式計數器、虛擬機器棧、本地方法棧這3個區域隨執行緒而生,隨執行緒而滅。每個棧幀中分配多少記憶體基本是在類結構確定下來時就已知的,因此這幾個區域的記憶體分配和回收都具備確定性。而java堆和方法區是執行緒共享的記憶體,且一個介面中的多個實現類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣,只有在程式執行期間才能知道會建立哪些物件,這部分記憶體的分配和回收都是動態的,垃圾收集器關注的是這部分記憶體。

2 判斷物件是否“存活”的方法:

2.1 引用計數法

定義:給物件新增一個引用計數器,每當有一個地方引用它,計數器就加1;當引用失效時,計數器就減1;計數器為0的物件是不可能再被使用的。

特點:實現簡單,判定效率高。但java虛擬機器沒用它,原因是它很難解決物件之間相互迴圈引用的問題。
如:

objA.instance=objB.instance;
objB.instance=objA.instance;
objA=null;
objB=null;

因為objA和objB互相引用,所以它們的引用計數器永遠不會為0,導致無法通知GC收集器回收它們。

2.2 可達性分析演算法

定義:用在Java,C#,Lisp中。通過稱為“GC Roots”的物件作為起始點,從這些節點向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有引用鏈相連時,該物件就不可用。

可作為GC Roots的物件包括:

  • 虛擬機器棧(棧幀中的本地變量表)中引用的物件
  • 方法區中靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 本地方法棧中JNI(Native方法)引用的物件

2.3 引用的分類

定義:reference型別的資料中儲存的數值代表的是另外一塊記憶體的起始地址,就稱這塊記憶體代表著一個引用。
引用的型別:

  • 強引用:類似“Object obj=new Object()”這類的引用,只要強引用還在,垃圾收集器不會回收被引用的物件。
  • 軟引用:用於描述一些有用但非必要的物件。在系統發生記憶體溢位異常之前,將把軟引用關聯著的物件列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,將丟擲記憶體溢位異常。用SoftReference類實現軟引用。
  • 弱引用:用於描述非必須的物件。被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。用WeakReference類實現弱引用。
  • 虛引用:也稱幽靈引用或幻影引用,是最弱的引用關係。不對物件的生存時間構成影響,也無法通過虛引用取得一個物件例項。一個物件設定虛引用關聯的唯一目的是這個物件被收集器回收時收到一個系統通知。用PhantomReference類實現虛引用。

3.4 生存還是死亡

即使可達性演算法中不可達的物件,也並非非死不可,這時處於緩刑階段,要真正回收至少需要經歷2次標記:
1)如果物件進行可達性分析後,發現沒有與GC Roots相連線的引用鏈,這時進行第一次標記並且進行一次篩選,刷選條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋該方法,或者該方法已經被虛擬機器呼叫過,虛擬機器將2種情況視為“沒必要執行”。
如果這個物件被判定為有必要執行finalize()方法,那麼這個物件將會放在F-Queue佇列中,並稍後由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行它。
稍後GC將對F-Queue中的物件進行第二次標記,若物件要在finalize()中拯救自己–只要重新與引用鏈上的任何一個物件建立關聯即可,如把把自己(this關鍵字)賦值給某個類變數或者物件的成員變數,那麼第二次標記時它將被移除“即將回收”的集合;如果這時候沒有拯救自己,那就真的被回收了。
注意:每個物件的finalize()方法只會被執行一次,如果拯救過一次,下次進行GC時,將不會再次執行finalize()進行自我拯救了。另外,finalize()執行代價高昂,不確定性大,無法保證各個物件的呼叫順序,所以要避免使用。

3.5 回收方法區

java虛擬機器規範中指出不要求在方法區(HotSpot虛擬機器中的永久代)進行垃圾收集,因為在方法區的垃圾收集價效比比較低:在堆中,尤其新生代,垃圾收集可回收70%~95%的空間,而永久代的垃圾收集效率遠低於此。

方法區(永久代)回收物件有2類:廢棄常量和無用的類。
1)廢棄常量的判定:以常量池中的“abc”為例,沒有String物件引用它,也沒有其它地方引用它,發生GC時,“abc”常量會被清理。常量池中的其它他類(介面)、方法、欄位的符號引用也與此類似。
2)“無用的類”的判定,同時滿足3個條件:

  • 該類的例項都被回收,即java堆中不存在該類的任何例項
  • 載入該類的ClassLoader已經被回收
  • 該類對應的java.lang.Class物件沒有在任何地方被引用,無法通過反射訪問該類的方法

注:無效的類不是必須要回收的。在大量使用反射、動態代理和頻繁自定義ClassLoader的場景都需要虛擬機器具備類解除安裝的功能,以保證永久代不會溢位。

4 垃圾收集方法

4.1 標記–清除演算法(老年代)

分2個階段:
1)首先標記處需要收回的物件
2)標記完成後統一回收被標記的物件(前面講過標記流程了)
在這裡插入圖片描述
不足:
1)標記和清除2個過程的效率不高
2)標記清除後產生大量記憶體碎片,可能無法為大物件分配空間時,找到足夠的連續記憶體而提前觸發另一次GC動作。

4.2 複製演算法(新生代)

將記憶體按容量分為大小相同的2塊,每次只用其中一塊,當這塊記憶體用完了,就將還存活的物件複製到另外一塊上面,然後把已使用過的記憶體空間清理掉,這樣使得每次清理都是對整個半區進行記憶體回收。
在這裡插入圖片描述
優點:沒有碎片,實現簡單,執行高效。
缺點:代價是將記憶體縮小為了原來的一半。

新生代中98%的物件是“朝生夕死”的,所以不需要按1:1劃分記憶體,而是將記憶體劃分為Eden和2個Survivor,其中Eden:Survivor=8:1。每次使用一個Eden和一個Survivor,回收時,將Eden和Survivor上存活的物件複製到另一個Survivor上,這樣每次新生代中可用的記憶體空間為整個新生代容量的90%,只有10%的記憶體被浪費。當另一個Survivor空間沒有足夠空間存放上一次新生代存活下來的物件時,這些物件將直接通過分配擔保機制進入老年代。

4.3 標記–整理演算法(老年代)

複製收集演算法在物件存活率高時要進行較多的複製操作,效率變低。根據老年代的特點,產生了“標記–整理”演算法,標記過程與“標記–清除”一樣,但後續不是直接清除可回收物件,而是讓所有存活物件移動到一端,直接清理掉端邊界以外的記憶體。
在這裡插入圖片描述

4.4 分代收集演算法

當前商業虛擬機器的垃圾收集都採用“分代收集”演算法,根據物件存活週期的不同將記憶體劃分位:新生代和老年代。新生代採用複製演算法,只需要複製少量的存活物件就可完成收集;老年代中物件存活率高、沒有額外空間對其擔保,所以採用標記--整理演算法或者標記--清除演算法進行回收。

5 HotSpot的演算法實現

5.1 列舉根節點(查詢根節點GCRoot)

1)停止所有的java執行執行緒(“stop the world”)
可達性分析必須在一致性的快照中進行,一致性指的是不可以出現分析過程中物件引用關係還在不斷變化的情況。這點是導致GC進行時必須停頓所有java執行緒的一個原因。
2)準確式GC:當系統停下來時,不需要一個不漏的檢查完所有執行上下文和全域性的引用位置。HotSpot的實現是:在特定位置上(即安全點),虛擬機器通過OopMap資料結構在類載入時,將物件內什麼偏移量上是什麼型別的資料計算出來,並存儲到其中,來達到這個目的。在OopMap的協助下,HotSpot可以快速且準確的完成GCRoots的列舉。

5.2 安全點 Safepoint

安全點:程式執行時只會在安全點發生GC。

安全點選定的依據:是否具有讓程式長時間執行的特徵。“長時間執行”的明顯特徵是指令序列複用,例如:方法呼叫、迴圈跳轉、異常跳轉等。

在GC發生時,如何讓所有的執行緒(不包括執行JNI呼叫的執行緒)都跑到最近的安全點上在停頓下來,2中方案:
1)搶先式中斷:不需要執行緒的執行程式碼主動去配合,在GC發生時,首先中斷所有的執行緒,如果發現執行緒沒有中斷在安全點上,就恢復執行緒,讓它跑到安全點上。現在幾乎沒有虛擬機器實現採用搶先式中斷來暫停執行緒從而響應GC事件。
2)主動式中斷:不直接對執行緒操作,簡單的設定一個標誌,各個執行緒執行時主動去輪詢這個標誌,發現中斷標誌為真時就自己中斷掛起。輪詢標誌的地方和安全點是重合的,另外在加上建立物件需要分配記憶體的地方。

5.3 安全區域

作用:Safepoint機制保證了程式執行時,在不太長的時間內就會遇到可進入GC的Safepoint。但是如果程式“不執行”的時候呢?即沒有分配CPU時(執行緒Sleep狀態或Blocked狀態),需要安全區域來解決。

定義:安全區域是指在一段程式碼片段之中,引用關係不會發生變化。在這個區域的任何地方開始GC都是安全的,可以把Safe Region看成是被擴充套件了的Safepoint。執行緒執行到Safe Region中的程式碼時,首先標識自己進入了Safe Region,JVM發起GC時,就不用管狀態為Safe Region的執行緒了。執行緒要離開Safe Region時,它要檢查系統是否完成了根節點列舉(或者整個GC過程),如果完成了,那執行緒就繼續執行,否則就必須等待直到收到可以安全離開Safe Region的訊號為止。

6 垃圾收集器(共7個)

如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。圖中,如果兩個收集器之間存在連線,就說明它們可以搭配使用。

注:掌握內容有:特點、適用物件,其次終點掌握CMS和G1。
在這裡插入圖片描述

6.1 Serial收集器(新生代,複製演算法)

■介紹:它是單執行緒的收集器,只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,並且進行垃圾收集時,必須暫停其它所有的工作執行緒,直到收集結束。

■缺點:stop the world給使用者帶來的體驗很差,因為垃圾收集的時候,使用者執行緒可能停頓。

■優點:簡單而高效(與其他收集器的單執行緒比),對於限定單個CPU的環境來說,Serial收集器由於沒有執行緒互動的開銷,專心做垃圾收集可以獲得最高的單執行緒收集效率。

■應用場景:Client模式下的虛擬機器

6.2 ParNew收集器(新生代,複製演算法)

■介紹:ParNew收集器是Serial的多執行緒版本,除了使用多執行緒進行垃圾收集之外,其餘行為包括Serial收集器可用所有控制引數(如:-XX:SurvivorRatio, -XX:PretenureSizeThreshold, -XX:HandlePromotionFailure等)、收集演算法、stop the world、物件分配規則、回收策略等都與Serial收集器完全一樣。

■應用場景:Server模式下的虛擬機器。除了Serial收集器外,只有它能與CMS收集器(老年代)配合工作。ParNew收集器是使用-XX:+UseConcMarkSweepGC選項後的預設新生代收集器,也可以使用-XX:+UseParNewGC選項來強制指定他。

ParNew收集器在單執行緒CPU的環境中不會比Serial收集器的效果好,當然隨著CPU的數量的增加,它對於GC時系統資源的有效利用還是有好處的。它預設開啟的收集執行緒數與CPU的數量相同,在CPU非常多的環境下,使用-XX:ParallelGCThreads引數來限制垃圾收集的執行緒數。

注意:並行和併發的區別
1)並行(Parallel):多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。
2)併發(Concurrent):指使用者執行緒和垃圾收集執行緒同時執行(但不一定是並行,可能交替執行),使用者程式在繼續執行,而垃圾收集程式執行在另一個CPU上。

6.2 Parallel Scavenge收集器(新生代,複製演算法)

■介紹:吞吐量優收集器是一個新生代收集器,採用複製演算法,是一個並行的多執行緒收集器。Parallel Scavenge收集器的目標是達到可控制的吞吐量(CPU執行使用者程式碼的時間/(使用者程式碼的時間+垃圾收集的時間))。

■應用場景:高吞吐量可以高效利用CPU時間,儘快完成程式的運算,適合在後臺運算而不需要太多互動的任務。

■控制吞吐量的2個引數:
1)最大垃圾收集停頓時間-XX:MaxGCPauseMillis:GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的
2)吞吐量大小-XX:GCTimeRatio:垃圾收集時間/總時間,即吞吐量的倒數。

■GC自使用調節引數:開關-XX:+UseAdaptiveSizePolicy
1)開啟時,只要把基本的記憶體資料設定好(如:-Xmx設定最大堆),然後使用MaxGCPauseMillis(更關注最大停頓時間)或者GCTimeRatio(更關注吞吐量)引數給虛擬機器設立一個優化目標,虛擬機器會動態調整具體細節引數(新生代大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、晉升老年代物件大小(-XX:PretenureSizeThreshold)),這種調節方式稱為GC自適應的調節策略

■Parallel Scavenge收集器與ParNew收集器區別:
1)關注點不同
2)Parallel Scavenge收集器有自適應調節策略

6.4 Serial Old收集器(老年代)

■介紹:Serial Old收集器是Serial收集器的老年代版本,是一個單執行緒收集器,使用“標記--整理”演算法。

■場景:主要意義在於:
1)給Client模式下的虛擬機器使用
2)在Server模式下,2大用途:
a. 在JDK1.5以及之前版本中與ParallelScavenge收集器搭配使用
b. 作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用

6.5 Parallel Old收集器(老年代)

■介紹:是Parallel Scavenge收集器的老年代版本,使用多執行緒“標記--整理”演算法。

■使用場景:和Parallel Scavenge收集器組合適用於注重吞吐量和CPU資源敏感的場合。

6.6 CMS收集器(老年代)

■CMS(Concurrent Mark Sweep)收集的目標是獲取最短回收停頓時間

■應用場景:java應用集中在網際網路站或者B/S系統的服務端上,這類應用尤其注重服務響應速度,希望停頓時間最短。

■採用標記–清除演算法,整個過程分4個步驟:
1)初始標記:僅僅標記一下GC Roots能直接關聯到的物件,速度很快。
2)併發標記:進行GC Roots Tracing
3)重新標記:修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄。
4)併發清除:停頓時間比初始標記稍長,但遠比並發標記上的時間短。

注:初始標記和重新標記仍需“stop the world”。耗時最長的併發標記和併發清除過程,收集器執行緒與使用者執行緒一起工作,所以總體來看,CMS收集器的記憶體回收過程是與使用者執行緒併發執行的。

■CMS(併發低停頓收集器)缺點:
1)CMS收集器對CPU資源非常敏感(併發設計的程式都對CPU資源敏感)。在併發階段,不會導致使用者執行緒停頓,但是會因佔用一部分執行緒而導致程式變慢,總吞吐量降低。CMS預設啟動的回收執行緒數是(CPU數量+3)/4,即CPU在4個以上時,併發回收垃圾收集執行緒不少於25%【(4+3)/4=1;1/4=0.25】的CPU資源。當CPU數量不足4個時,CMS對使用者程式的影響會變得很大,所以虛擬機器提供了“增量式併發收集器 i-CMS”CMS的變種,就是在併發標記、清理的時候讓GC執行緒、使用者執行緒交替執行,儘量減少GC執行緒的獨佔資源的時間,這樣整個垃圾收集時間會更長,但對使用者的影響會變小。然而 i-CMS的效果很一般,已經被宣告“deprecated”,不提倡使用者使用。
2)CMS無法收集浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。併發清理階段,因與使用者執行緒並行,產生的新的垃圾出,這部分垃圾就是浮動垃圾。由於垃圾收集階段使用者執行緒在執行,就需要預留足夠的記憶體給使用者執行緒使用,因此CMS不能像其他收集器那樣等到老年代幾乎填滿了在進行收集。JDK1.5預設CMS收集器當老年代使用了68%就會被啟用,如果老年代增長不是太快,可調高-XX:CMSInitiatingOccupanyFraction引數來提高出發百分比,以降低迴收次數而獲得好的效能,JDK1.6中,CMS閾值是92%,要是CMS預留的記憶體無法滿足程式需求,會出現“Concurrent Mode Failure”失敗,這時虛擬機器啟動後備方案:臨時啟用Serial Old收集器重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以-XX:CMSInitiatingOccupanyFraction引數設的太高容易導致大量“Concurrent Mode Failure”失敗,效能反而降低。
3)CMS是基於“標記--清除”演算法實現的收集器,收集結束的時候會有大量空間碎片產生,可能無法給大物件找到足夠的、連續的記憶體,不得不提前觸發Full GC。CMS提供了-::UseCMSCompactAtFullCollection開關引數(預設開啟),開啟碎片的合併整理過程,記憶體整理是無法併發的,空間碎片問題沒了,但是停頓時間變長了。虛擬機器提供了-xx:CMSFullGCcBeforeCompaction引數用於設定執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的(預設值為0,即每次Full GC時都會進行碎片整理)。

6.7 C1收集器(新生代+老年代)

◆工作原理:多執行緒、標記-整理演算法

◆適用物件:面向服務端應用

◆歷史地位:當今收集器技術發展的最前沿成果之一

◆4個特點:

a、能充分利用多CPU、多核環境下的硬體優勢。部分其他收集器需要停頓java執行緒的操作,G1收集器可以通過併發方式讓java程式繼續執行。

b、可以獨立管理整個GC堆。同時根據不同物件(新建立的物件、已經存活一段時間的物件、熬過多次GC的舊物件)使用不同處理方式。

c、整體看,基於“標記-整理”演算法;區域性看,基於“複製”演算法。不會產生碎片。

d、可預測停頓時間、降低停頓。降低停頓時間是CMS的一巨大優勢。預測停頓時間,讓使用者明確指定在長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不超過N毫秒。原理:

注1:可預測實質:有計劃的避免在整個java堆進行全區域的垃圾收集。而G1之前的收集器的收集範圍都是對整個新生代和整個老年代

注2:可預測原理:後臺維護一個優先列表,列表裡面存放了G1跟蹤各個Region裡面的垃圾堆積的價值的大小。每次回收,根據優先列表,優先回收價值最大的Region。這保證了G1在有限時間內儘可能高的收集效率。

◆記憶體佈局:採用G1管理記憶體時,記憶體被分為很多個大小相等的獨立區域(Region)。新生代、老年代不再是物理隔離,它們是一部分Region的集合。

注1: Remembered Set用於解決問題:Region之間的物件引用;其他收集器的新生代和老生代之間的物件引用。

注2:Remembered Set工作機制:G1中每個Region都維護一個RememberedSet,虛擬機發現程式在對Reference型別的資料進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的物件是否處於不同的Region之中。如果是,便通過CardTable把相關引用資訊記錄到被引用物件所屬的Region的Remembered Set中。當進行垃圾收集時,在GC根節點的列舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

◆和CMS對比:都立足於低停頓。G1雖然不錯,但距離成熟釋出版本是時間比較短,經歷的實際應用的考驗比較少。所以在現有的收集器沒有出現什麼問題的情況下,沒有理由選擇G1。如果追求低停頓可選擇CMS,如果追求高吞吐量G1不會帶來好的表現。

◆運作步驟:
1)初始標記(停頓程式,單執行緒標記,耗時很短):只是標記GC Roots直接關聯到的物件,且修改(next top at mark start)值,讓下一階段使用者程式併發執行時,能在可用的region中建立物件。
2)併發標記(與使用者程式併發執行):從GC Roots對堆中物件進行可達性分析,找出存活物件,耗時較長,與程式併發進行。
3)最終標記(停頓程式,並行執行):虛擬機器把併發標記期間因使用者程式繼續執行而導致標記產生變動的標記記錄記錄在Remembered Set Logs中,此階段把logs中資料合併到Remembered Set中。
4)篩選回收(與使用者程式併發執行,並行執行):先對各個region的回收價值和成本排序,在根據使用者期望的GC停頓時間制定回收計劃。

7 GC日誌格式

注:每種收集器的日誌格式都有它們自身去實現,即可以不一樣。但為方便使用者閱讀,虛擬機器的設計者將各個收集器的日誌都維持了一定的共性。下面對一個典型的例子說明一下各個部分的含義。
例項:
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
解釋:
33.125:代表了GC發生的時間,這個數字的含義是從JVM啟動以來經過的秒數。
[GC和[Full GC說明了垃圾回收的型別。如是Full GC時,表示這次GC發生了“Stop the World”(因為出現了分配擔保失敗之類的問題,所以才導致STW)。System.gc()方法出發的收集將顯示[Full GC(System)。
[DefNew:表示GC發生的區域(新生代、老年代),區域名稱與使用的GC收集器密切相關。
Serial收集器,新生代名:Default New Generation,區域名:[DefNew
ParNew收集器,新生代名為:Parallel New Genetaion,區域名:[ParNew
Parallel Scavenge收集器,新生代名為:PSYoungGen
老年代和永久代同理,名稱由收集器決定。
3324K->152K(3712K)表示GC前該記憶體區域內已使用容量->GC後該記憶體區域已使用容量(該區域總容量)。0.0025925 secs表示該區域GC的耗時,有的收集器會給出詳細的耗時,如:[Times: User=0.01 sys=0.00, real=0.02secs],這裡user表示使用者態消耗CPU時間,sys表示核心態消耗的CPU事件,real表示操作從開始到結束經過的牆鍾時間。CPU時間和牆鍾時間的區別是:牆鍾時間包括各種非計算的等待耗時(等待磁碟I/O、等到執行緒阻塞),而CPU時間不包括這些耗時。當系統有多CPU或多核的話,多執行緒會疊加這些CPU時間。

3324K->152K(11904K)表示GC前java堆已使用容量->GC後java堆已使用容量(java堆總容量)。

8 記憶體分配與回收策略

java技術體系的自動記憶體管理可以歸結為自動化解決了2問題:
1)給物件分配記憶體
2)回收分配給物件的記憶體
物件的記憶體分配,是在堆上分配(也可能經過JIT編譯器編譯後,被拆分為標量型別,從而分配在棧上)。
物件主要分配在新生代的Eden區,如果啟動了本地執行緒分配緩衝,將按執行緒優先在TLAB上分配,細節取決於用哪種垃圾收集器組合和虛擬機器中與記憶體相關的引數。

下面講解給物件分配記憶體的策略:

8.1 物件優先在Eden分配

★物件優先分配在Eden,如果Eden記憶體不足,將觸發一次Minor GC。

★區分新生代GC(MinorGC) && 老年代GC(Major GC/Full GC)

  • Minor GC:指發生在新生代的GC。因為java物件基本都是朝生夕滅,故Minor GC非常頻繁,速度也比較快。
  • Major GC:指發生在老年代的GC。出現Major GC,通常伴隨一次Minor GC,但非絕對。速度比Minor GC慢10倍以上。

8.2 大物件直接進入老年代

★大物件定義:需要大量連續記憶體的java物件(如:new byte[2*1024])。最典型的就是很長的字串或者陣列。

★物件直接進老年代的原因:經常出現大物件容易導致記憶體還有不少空間時就提前觸發GC以獲取更大的連續空間來儲存新物件。

★引數:-XX:PretenureSizeThreshold引數。當物件所需記憶體大小大於這個引數值時,物件直接在老年代分配記憶體。

★目的:避免在Eden區及兩個Suivivor區之間發生大量的記憶體複製(新生代採用複製演算法收集記憶體)。

8.3 長期存活的物件將進入老年代

★JVM為每個物件都設定一個物件年齡計數器。

★年齡增長機制:如果物件在Eden區出生並經過第一次GC後仍存活,並且能被Survivor容納,將被移動到Survivor空間,並且年齡設定為1。以後在Survivor每經歷過一次Minor GC,年齡就增長1歲,到15歲後(預設),將會被晉升為老年代。可以通過-XX:MaxTenuringThreshold引數設定年齡閾值。

8.4 動態物件年齡判定

★目的:為更好的適應不同程式的記憶體狀況,JVM並不是用要求物件的年齡達到年齡閾值後才能進入老年代。

★實現原理:如果在Survivor區中,相同年齡的所有物件大小總和大於其空間大小的一半。則年齡大於或等於該年齡的物件可以直接進入老年代。

8.5 空間分配擔保

★工作機制:在發生MinorGC之前,JVM會檢查老年代最大可用的連續空間是否大於新生代物件總空間:
1)如果大於,則此次GC是安全的;
2)如果小於,JVM會檢視HandlePromotionFailure設定值是否允許失敗:

  • 如果允許擔保失敗:JVM會繼續檢查老年代最大連續記憶體大小是否大於歷次晉升到老年代物件的平均大小:
    • 如果大於,就嘗試進行一次Minor GC,雖然這樣做是有風險的。
    • 如果小於,就進行一次Full GC(Major GC)。
  • 不允許擔保失敗:就進行一次Full GC(Major GC)。

★冒險
1)實質:老年代接納Survivor容納不下的存活下來的新生物件。
2)為什麼說是冒險:因為新生代存活下來,進行Minor GC時,是將其從Eden區複製到Survivor區。考慮極端情況,當Eden區所有物件都存活下來了,那麼就需要將所有的物件複製到未被使用的那個Survivor區。顯然此時Survivor是裝不下這麼多物件的。此時裝不下的物件就要被放到老年代當中。也就是老年代為其做了擔保,但是老年代剩餘的空間不一定能容納存活下來的物件,所以Minor GC不一定成功,即有風險。

注:JDK6後,HandlePromotionFailure引數就不起作用了,判斷的規則和HandlePromotionFailure開啟是的規則是一樣的。

ps:終於寫完了,天啊,真心累啊~~~喜歡的話,點個讚唄O(∩_∩)O哈哈~