JVM垃圾回收機制與內存回收


暫時轉於:https://blog.csdn.net/qq_27035123/article/details/72857739

垃圾回收機制

 

GC是垃圾回收機制,java中將內存管理交給垃圾回收機制,這是因為在面向對象編程中一個對象的生命周期往往無法預料,所以我們無法為每個對象指定回收時機。

 

但是我們可以采用System.gc()Runtime.getRuntime().gc()進行請求垃圾回收,可以使用對象的finalize()對必要資源在垃圾回收之前進行處理。

 

優點:使得java程序員不需要考慮內存管理,由於垃圾回收機制,java中的對象不再有作用域的限制,只有對象的引用有作用域,可以有效的防止內存泄漏,有效的使用有限的可以使用的內存。

 

垃圾收集算法

 

  1. 標記-清掃(Mark-and-sweep)—sun前期版本就是用這個技術。 原理:對於“活”的對象,一定可以追溯到其存活在堆棧、靜態存儲區之中的引用。這個引用鏈條可能會穿過數個對象層次。第一階段:從GC roots開始遍歷所有的引用,對有活的對象進行標記。第二階段:對堆進行遍歷,把未標記的對象進行清除。這個解決了循環引用的問題。 缺點:1、暫停整個應用;2、會產生內存碎片。

  2. 復制(copying) 原理:為了提升效率,把內存空間划分為2個相等的區域,每次只使用一個區域。垃圾回收時,遍歷當前使用區域,把正在使用的對象復制到另外一個區域。優點:不會出現碎片問題。 缺點:1、暫停整個應用。2、需要2倍的內存空間。

  3. 標記-整理(Mark-Compact) 原理:第一階段標記活的對象,第二階段把為標記的對象壓縮到堆的其中一塊,按順序放。即將所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內存。優點:1、避免標記掃描的碎片問題;2、避免停止復制的空間問題。 具體使用什么方法GC,Java虛擬機會進行監視,如果所有對象都很穩定,垃圾回收器的效率低的話,就切換到“標記-掃描”方式;同樣,Java虛擬機會跟蹤“標記-掃描”的效果,要是堆空間碎片出現很多碎片,就會切換回“停止-復制”模式。這就是自適應的技術。

  4. 分代(generational collecting)—–J2SE1.2以后使用此算法 原理:基於對象生命周期分析得出的垃圾回收算法。把對象分為年輕代、年老代、持久代,對不同的生命周期使用不同的算法(2-3方法中的一個即4自適應)進行回收。

    • 新生代:每次垃圾收集都有大量對象死去,只有少量存活,就選擇復制算法

    • 老年代:對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”,或者“標記-整理”算法來進行回收。

  5. 自適應算法(Adaptive Collector)在特定的情況下,一些垃圾收集算法會優於其它算法。基於Adaptive算法的垃圾收集器就是監控當前堆的使用情況,並將選擇適當算法的垃圾收集器。

 

設想一下,如果我們作為一個GC的話,我們會如何進行垃圾回收?就像如何將一頭大象放入冰箱,我們的垃圾回收也可以分為三步來實現:

 

  1. 哪些內存需要進行回收?

  2. 什么時候回收?

  3. 怎么進行內存回收?

 

如何判斷那些內存需要回收?

 

兩種熟知的方法

 

同樣我們站在GC的角度思考,對象存在的意義就是為了被引用,那么利用計數器,沒有被引用的對象是不是可以看作死亡了呢?

 

引用計數法:如果有地方引用該對象,該對象的引用計數就+1,如果引用失效的話就減一。計數器為0的對象不可以被使用。

 

答案是不行的。試想一下,如果有兩個對象互相引用,比如objA.instance = objB, objB.instance = objB,這個時候兩個對象都不能被訪問,但是互相引用導致引用計數不為0,這不就無法判定為死亡了嗎?我們如果是GC,能允許這種長生不死的存在嗎?肯定不。所以引用計數法並沒有被采用在目前的JVM垃圾回收器中。

 

可達性分析法:如果我們將一些GC Roots對象作為起始點,從這些節點向下搜索,搜索到的路徑為引用鏈,如果有一些對象沒有任何引用鏈相連,那么這個對象對於GC Roots是不可達的,即使它們之間可能相互產生關聯,所以將其判定為可回收對象。如下圖:

 

GC Roots:

 

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象

  • 方法區中類靜態屬性引用的對象

  • 方法區中常量引用的對象

  • 本地方法棧中JNI(即一般說的Native方法)引用的對象

 

那么什么是引用呢?

 

jdk1.2之前,定義為:如果reference類型的數據中存儲的數值代表的是另一塊內存的起始地址,就成為這塊內存代表着一個引用。

 

那么我們好像對於那種如果希望內存足夠的時候保留,不夠的時候回收的對象一個十分明確的解釋。

 

jdk1.2之后,擴展為:

 

  • 強引用:只要存在,垃圾收集器就不會回收對象。

    Object obj = new Object();之類

  • 軟引用:用來描述一些還有用但是不必須的對象,系統將要發生內存溢出異常之前,將會把這些對象列入回收范圍之中進行第二次回收,如果還是不夠那就只能拋出內存溢出的異常了。

    SoftReference<String>s = new SoftReference<>(“我還有用但不是必須的!”);

  • 弱引用:用來描述非必須對象,但是強度比弱引用更弱,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前,垃圾收集工作的時候,無論是否必要都會回收掉只被弱引用關聯的對象。

    WeakReference<String>s = new WeakReference<String>(“我只能活到下一次垃圾收集之前”);

  • 虛引用(幽靈引用或幻影引用):一個對象是否有虛引用,與其生命周期毫無關系,也無法通過虛引用取得一個對象實例,只被虛引用的對象,隨時都會被回收掉

    PhantomReference<String>ref = new PhantomReference<String>(“我只能接受死亡通知”) , targetReferenceQueue<String>);

 

不可達對象非死不可嗎

 1 public class SaveSelf {
 2     public static SaveSelf instance = null;
 3 
 4     @Override
 5     protected void finalize() throws Throwable {
 6         super.finalize();
 7         System.out.println("finalize method executed!");
 8         instance = this;
 9     }
10     public void isAlive(){
11         System.out.println("I'm alive!");
12     }
13 
14     public static void main(String[] args) {
15         instance = new SaveSelf();
16         instance = null;
17         System.gc();
18         try {
19             Thread.sleep(500);//用於等待Finalize線程執行finalize方法
20             if (instance != null){
21                 instance.isAlive();
22             }else{
23                 System.out.println("I will dead!");
24             }
25         } catch (InterruptedException e) {
26             e.printStackTrace();
27         }
28 
29         instance = null;
30         System.gc();
31         try {
32             Thread.sleep(500);
33             if (instance != null){
34                 instance.isAlive();
35             }else{
36                 System.out.println("I will dead!");
37             }
38         } catch (InterruptedException e) {
39             e.printStackTrace();
40         }
41     }
42 }
    finalize method executed!

    I’m alive!

    I will dead!

    Process finished with exit code 0
 

 

任何一個兌現過的finalize方法都會只被系統自動調用一次。當然finalize方法一般用來回收一些外部資源。

回收方法區

方法區也是有垃圾收集的,那么為什么會有呢?因為如果常量池中存在一個”abc”,而沒有任何的String對象引用常量“abc”,那么我們需要對這個常量進行回收的。另外永久代的垃圾收集主要包括廢棄常量和無用的類。

判定一個廢棄常量很簡單,那么如何判定一個無用的類(類對象比如Integer)呢?

  • 該類的所有實例都已經被回收,也就是java堆中不存在任何該類的實例

  • 加載該類的ClassLoader已經被回收

  • 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過訪問該類的方法。

當然這里的是可以回收,但不一定必然回收。

HotSpot中的實現

枚舉根節點

可達性分析對於時間的敏感性:可達性分析的過程必須在一個確保一致性的快照中進行–這里“一致性”的意思是指在整個分析的過程中執行系統看起來像是被凍結在某個時間點,不可以出現分析過程中對象的引用關系還在不斷變化的情況,這樣子分析結果的准確性就無法得到保證。這點是導致GC進行時必須停頓所有執行線程(Stop the world)的一個重要原因,即使是在號稱(幾乎)不會發生停頓的CMS收集器中,梅菊根結點也是必須要停頓的。

在HotSpot虛擬機中,使用了一組成為OopMap的數據結構來達到這個目的的,在類加載完成的時候,HotSpo就可以將對象內什么偏移量上是什么類型的數據計算出來,在JIT編譯過程中,也會在特定的位置記錄下戰和寄存器中哪些位置是引用。

安全點

HotSpot沒有為每條指令都生成OopMap,因為這需要大量的內存空間,所以只是在“特定的位置”記錄了這些信息,這些位置稱為安全點(SafePoint),即程序執行時並非在所有地方都能停頓下來開始GC,只有在到達安全點的時候才能進行暫停。

安全點的選定基本上是以程序“是否具有讓程序長時間執行的特征”為標准進行選定的–因為每條指令執行的時間都非常短暫,程序不太可能因為指令流長度太長這個原因而長時間運行,而“長時間執行”最明顯的特征就是指令序列服用,例如方法調用、循環跳轉、一場跳轉等,所以具有這些功能的指令才會產生Safepoint。

如何中斷

  1. 搶先式中斷:不需要線程的執行代碼主動的配合,在GC執行的時候,首先把所有線程全部中斷,如果發現有線程中斷的地方不再安全點上,就恢復線程,讓它“跑”到安全點上。
  2. 主動式中斷:當GC需要進行中斷線程的時候,不直接對線程操作,僅僅簡單的設置一個標志,所個線程執行時主動去輪詢這個標志,發現中斷標志為真時就自己中斷掛起,輪詢標志的地方和安全點是重合的,另外再加上創建對象的時候需要分配內存的地方。

幾乎沒有虛擬機采用搶先式中斷!

安全區域

使用安全點的方法保證了程序執行時,在不太長的時間內就會遇到可以進入GC的安全點,但是如果程序不執行的時候,比如所謂的程序不執行就是沒有分配到CPU時間,即處於Sleep狀態或者Blocked狀態,這時候線程無法響應JVM的中斷請求,“走“到安全的地方去中斷掛起,JVM顯然不太可能等待線程重新被分配CPU時間。

安全區域是指在一段代碼片段中,引用關系不會發生改變,這個區域中的任何位置開始進行GC都是安全的,我們可以把安全區域看作是拓展了的安全點。

當線程執行到安全區域的時候,首先標識自己進入了安全區域,那樣,當這段時間里JVM要發起GC的時候,就不需要管標識自己為安全區域的線程,在線程要離開安全區域時,它要檢查系統是否完成了根節點枚舉(或者是整個GC過程),如果要完成了,線程就繼續執行,否則就必須等待直到回收過程完成並可以安全離開安全區域的信號為止。

垃圾收集器

上圖展示了7種作用域不同分代的收集器,如果兩個收集器之間存在連線,就說明他們可以搭配使用,虛擬機所處的區域,則表示它是屬於新生代收集器還是老年代收集器。

1. Serial(串行GC)收集器

Serial收集器是一個新生代收集器,單線程執行,使用復制算法。它在進行垃圾收集時,必須暫停其他所有的工作線程(用戶線程)。是Jvm client模式下默認的新生代收集器。對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。

2. ParNew(並行GC)收集器

ParNew收集器其實就是serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其余行為與Serial收集器一樣。在單CPU工作環境內絕對不會有比Serial的收集器有更好的效果,隨着可以使用的CPU的數量的增加,它對於GC時系統資源的有效利用還是很有好處的,它默認開啟的收集線程數與CPU的數量下同,在CPU非常多的環境下,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。

3. Parallel Scavenge(並行回收GC)收集器

Parallel Scavenge收集器也是一個新生代收集器,它也是使用復制算法的收集器,又是並行多線程收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鍾。其中垃圾收集花掉1分鍾,那吞吐量就是99%。Parallel Scavenge提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數和直接設置吞吐量大小的-XXGCTimeRatio參數。

這個收集器還有一個開關:-XX:+UseAdaptiveSizePolicy值得關注。這個開關打開后,虛擬機會根據當前系統的運行情況收集性能監控信息自動調整新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRation)、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細節參數。

使用自適應策略,只需要設置最大堆(-Xmx),利用最大停頓時間或者吞吐量給虛擬機設置一個優化目標。

4. Serial Old(串行GC)收集器

Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“標記-整理”算法。主要使用在Client模式下的虛擬機。對於Server模式下有兩個用途:1. 在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用;2. 作為CMS收集器的后備預案,在並發收集發生Concurrent Mode Failure時使用。

5. Parallel Old(並行GC)收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。

6. CMS(並發GC)收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器是基於“標記-清除”算法實現的,整個收集過程大致分為4個步驟:

①.初始標記(CMS initial mark)

②.並發標記(CMS concurrenr mark)

③.重新標記(CMS remark)

④.並發清除(CMS concurrent sweep)

其中初始標記、重新標記這兩個步驟任然需要停頓其他用戶線程。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,並發標記階段是進行GC ROOTS 根搜索算法階段,會判定對象是否存活。而重新標記階段則是為了修正並發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
由於整個過程中耗時最長的並發標記和並發清除過程中,收集器線程都可以與用戶線程一起工作,所以整體來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。
CMS收集器的優點:並發收集、低停頓

CMS收集器主要有三個顯著缺點:
1. CMS收集器對CPU資源非常敏感。在並發階段,雖然不會導致用戶線程停頓,但是會占用CPU資源而導致引用程序變慢,總吞吐量下降。CMS默認啟動的回收線程數是:(CPU數量+3) / 4。虛擬機提供了一種稱為“增量式並發收集器”的CMS收集器變種,可以在並發標記、清理的時候讓GC線程、用戶線程交替運行,盡量減少GC線程的獨占資源的時間。
2. CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗后而導致另一次Full GC的產生。由於CMS並發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之后,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,即需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分內存空間提供並發收集時的程序運作使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低內存回收次數提高性能。要是CMS運行期間預留的內存無法滿足程序其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機將啟動后備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置的過高將會很容易致“Concurrent Mode Failure”失敗,性能反而降低。
3. 碎片化,最后一個缺點,CMS是基於“標記-清除”算法實現的收集器,使用“標記-清除”算法收集后,會產生大量碎片。空間碎片太多時,將會給對象分配帶來很多麻煩,比如說大對象,內存空間找不到連續的空間來分配不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full GC之后增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC之后,跟着來一次碎片整理過程。

7. G1收集器

G1(Garbage First)收集器是JDK1.7提供的一個新收集器,G1收集器基於“標記-整理”算法實現,也就是說不會產生內存碎片。還有一個特點之前的收集器進行收集的范圍都是整個新生代或老年代,而G1將整個Java堆(包括新生代,老年代)。

G1收集器的特點:

  1. **並行與並發:**G1利用多CPU、多核環境下的硬件優勢,縮小stop-the-world的時間。
  2. **分代收集:**G1不需要其他收集器配合就可以獨立管理整個GC堆,但它能夠采用不同的方式來處理。
  3. 空間整合:整體上是“標記-整理”,局部上是基於“復制”的算法來實現的
  4. 可預測的停頓:降低停頓時間,G1建立了可預測的停頓時間模型,能讓使用者明確的指定在一個長度M毫秒內的時間片段,消耗在垃圾收集的時間不得超過N毫秒,這已經適實時java(RTSJ)的垃圾收集器的特征了

G1收集的步驟:

  1. 初始標記
  2. 並發標記
  3. 最終標記
  4. 篩選回收

理解GC日志

GC日志格式:

GC發生的時間 + GC的類型(GC/Full GC) + GC發生的區域(收集器決定的名稱) + GC前java堆已使用容量 -> GC后java堆已使用容量(java堆總容量) + GC所占用的時間(秒)如:[Times : user = 0.01 sys = 0.00 real = 0.02 secs] 分別表示用戶態消耗的時間,內核態消耗的時間和操作從開始到結束所經過的牆鍾時間。

垃圾收集器參數總結

參數 描述
-XX:+UseSerialGC Jvm運行在Client模式下的默認值,打開此開關后,使用Serial + Serial Old的收集器組合進行內存回收
-XX:+UseParNewGC 打開此開關后,使用ParNew + Serial Old的收集器進行垃圾回收
-XX:+UseConcMarkSweepGC 使用ParNew + CMS + Serial Old的收集器組合進行內存回收,Serial Old作為CMS出現“Concurrent Mode Failure”失敗后的后備收集器使用。
-XX:+UseParallelGC Jvm運行在Server模式下的默認值,打開此開關后,使用Parallel Scavenge + Serial Old的收集器組合進行回收
-XX:+UseParallelOldGC 使用Parallel Scavenge + Parallel Old的收集器組合進行回收
-XX:SurvivorRatio 新生代中Eden區域與Survivor區域的容量比值,默認為8,代表Eden:Subrvivor = 8:1
-XX:PretenureSizeThreshold 直接晉升到老年代對象的大小,設置這個參數后,大於這個參數的對象將直接在老年代分配
-XX:MaxTenuringThreshold 晉升到老年代的對象年齡,每次Minor GC之后,年齡就加1,當超過這個參數的值時進入老年代
-XX:UseAdaptiveSizePolicy 動態調整java堆中各個區域的大小以及進入老年代的年齡
-XX:+HandlePromotionFailure 是否允許新生代收集擔保,進行一次minor gc后, 另一塊Survivor空間不足時,將直接會在老年代中保留
-XX:ParallelGCThreads 設置並行GC進行內存回收的線程數
-XX:GCTimeRatio GC時間占總時間的比列,默認值為99,即允許1%的GC時間,僅在使用Parallel Scavenge 收集器時有效
-XX:MaxGCPauseMillis 設置GC的最大停頓時間,在Parallel Scavenge 收集器下有效
-XX:CMSInitiatingOccupancyFraction 設置CMS收集器在老年代空間被使用多少后出發垃圾收集,默認值為68%,僅在CMS收集器時有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection 由於CMS收集器會產生碎片,此參數設置在垃圾收集器后是否需要一次內存碎片整理過程,僅在CMS收集器時有效
-XX:+CMSFullGCBeforeCompaction 設置CMS收集器在進行若干次垃圾收集后再進行一次內存碎片整理過程,通常與UseCMSCompactAtFullCollection參數一起使用
-XX:+UseFastAccessorMethods 原始類型優化
-XX:+DisableExplicitGC 是否關閉手動System.gc
-XX:+CMSParallelRemarkEnabled 降低標記停頓
-XX:LargePageSizeInBytes 內存頁的大小不可設置過大,會影響Perm的大小,-XX:LargePageSizeInBytes=128m

Client、Server模式默認GC

新生代GC方式 老年代和持久 GC方式
  Client Serial 串行GC Serial Old 串行GC
  Server Parallel Scavenge 並行回收GC Parallel Old 並行GC

Sun/OracleJDK GC組合方式

新生代GC方式 老年代和持久 GC方式
  -XX:+UseSerialGC Serial 串行GC Serial Old 串行GC
  -XX:+UseParallelGC Parallel Scavenge 並行回收GC Serial Old 並行GC
  -XX:+UseConcMarkSweepGC ParNew 並行GC CMS 並發GC 當出現“Concurrent Mode Failure”時 采用Serial Old 串行GC
  -XX:+UseParNewGC ParNew 並行GC Serial Old 串行GC
  -XX:+UseParallelOldGC Parallel Scavenge 並行回收GC Parallel Old 並行GC
  -XX:+UseConcMarkSweepGC -XX:+UseParNewGC Serial 串行GC CMS 並發GC 當出現“Concurrent Mode Failure”時 采用Serial Old 串行GC

內存分配與回收策略

對象的內存分配,往大方向講,就是在堆上分配,對象主要分配在新生代的Eden區,如果啟動了本地縣城分配緩存,將按照線程優先在TLAB上分配,少數情況下也可能會直接分配在老年代中,分配規則並不是百分百固定的,其細節決定於當前使用的是哪一種垃圾收集器組合,還有虛擬機中與內存相關的參數的設置。

1. 對象優先在Eden分配

大多數情況下,對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機將進行一次Minor GC。

Minor GC和Full GC有什么不一樣嗎?

新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。

老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨着至少一次的Minor GC(但非絕對,在Parallel Scavenge收集器的手機策略里有直接進行Major GC的策略選擇過程)Major GC的速度一般會比Minor GC慢10倍以上。

2. 大對象直接進入老年代

所謂的大對象是指,需要大量連續內存空間的java對象,最典型的大對象就是那種很長的字符串以及數組,大對象對虛擬機的內存分配來說就是一個壞消息,經常出現大對象容易導致內存還有不少空間時就提前觸發垃圾收集以獲取足夠的連續時間來“安置”它們。虛擬機提供了-XX:PretenureSizeThreshold參數,令大於這個設置值的對戲那個直接在老年代分配,這樣做的目的是避免在Eden區以及Survivor區之間發生大量的內存復制。

-XX:PretenureSizeThreshold參數只對Serial和ParNew兩款收集器有效,Parallel Scavenge的收集器不認識這個參數,Parallel Scavenge收集器一般並不需要設置,如果遇到必須使用這個參數的場合,可以考慮ParNew加CMS的收集器組合。

3. 長期存活的對象將進入老年代

虛擬機為了采用分代收集的思想來管理內存,利用每個對象定義的對象年齡(Age)計數器,如果對象在Eden出生並經歷了第一次Minor GC后仍然存活,並且能被Survivor接納的話,將移動到Survivor空間中,並且對象年齡設為1,對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當他的年齡增加到一定的程度(默認為15歲),就會被晉升到老年代,對象晉升到老年代的年齡閥值,可以通過參數-XX:MaxTenuringThreshold設置。

4. 動態對象年齡判定

為了更好的適應不同的成都的內存狀況,虛擬機並不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

5. 空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對戲那個總空間,如果這個條件成立,那么Minor GC可以確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗,如果允許,那么會繼續檢查老年代最大可用的連續控件是否大於歷次晉升到老年代對象的平均大小,如果大於,將嘗試着進行一次Minor GC,盡管這次Minor GC是有風險的,如果小於,或者HandlePromotionFailure設置不允許冒險,那這也要改為進行一次Full GC。

冒險:前面提到過,新生代使用復制收集算法,但為了內存利用率,只是用其中一個Survivor空間來作為輪換備份,因此當出現大量對象在Minor GC后仍然存活的情況(最極端的情況就是內存回收后新生代中所有對象都存活),就需要老年代進行分配擔保,把Survivor無法容納的對象直接進入老年代。

取平均值進行比較其實仍然是一種動態概率的手段,也就是說,如果某次Minor GC存活后的對象突增,遠遠高於平均值的話,依然會導致擔保失敗,如果發生了擔保失敗,那就治好在失敗后重新發生一次Full GC,雖然擔保失敗的時候繞的圈子是最大的,但大部分的時候還是會打開分配擔保的,避免Full GC過於頻繁。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM