Java的垃圾回收和內存分配策略


本文是《深入理解Java虛擬機 JVM高級特性與最佳實踐》的讀書筆記


 

在介紹Java的垃圾回收方法之前,我們先來了解一下Java虛擬機在執行Java程序的過程中把它管理的內存划分為若干個不同的的數據區的什么?

1.Java運行時數據區的划分

如下圖:

其中程序計數器,虛擬機棧,本地方法棧這3個區域的內存隨線程而生,隨線程而滅的,因此這幾個區域的內存分配與回收都是有確定的,我們不需要考慮這幾個區域的內存的分配與回收。而堆和方法區則不一樣,我們只有在程序處於運行期間時才能知道會創建哪些對象,這部分的內存的分配和回收都是動態的,垃圾收集器關注的就是這部分內存(堆和方法區)。

下面我們先簡單介紹一下這幾部分區域存放的什么東西;

  1. 程序計數器:(線程私有)當前線程所執行的字節碼的行號指示器,解釋器工作時就是通過改變這個計數器的值來取得下一條需要執行的指令。
  2. Java虛擬機棧(線程私有)描述的是Java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀(Stack Frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每個方法從刻調用直到執行完成,就對應於棧幀在虛擬機棧中的入棧和出棧的過程。我們常說的棧內存就是這個。
  3. 本地方法棧(線程私有)與Java虛擬機棧類似,只不過這是為虛擬機會用到的Native方法服務的,它也會拋出StackOverflowError和OutOfMemoryError異常。
  4. Java堆:(所有線程共享)幾乎所有的對象實例都會在這里分配內存,Java堆還可以細分為新生代和老年代;
  5. 方法區:(線程共享)用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據;(在某些虛擬機上也稱為永久代)。

 2 Java(JVM)的垃圾回收機制

2.1 哪些內存需要回收?

在Java中,都是通過可達性分析來對象是否存活的(如果對象是死的,那么它所占用的內存就是需要回收的)。可達性分析算法的基本思想就是通過一系列被稱為“GC Roots”的對象開始,從這些節點向下搜索,搜索走過的路線稱為引用鏈。當一個對象沒有在任何引用鏈上出現,則這個對象會被判定為不可用的(死的,可回收的)。

  在被可達性分析算法判定為不可用的對象,也並非是一定就是會被回收的,它們還會經歷一次篩選的過程,篩選的條件就是此對象是不是要執行finalize()方法,如果對象沒有覆蓋finalize()方法或它的finalize()方法在上一次垃圾回收器工作時已經執行過了,則被判定為不用執行finalize()方法(對象會在這次回收中被回收),若判定為需要執行finalize()方法,則這個對象會被放置在一個F-Queue隊列中,稍后虛擬機會建立一個finalizer線程(低優先級)來觸發這個方法,但虛擬機不承諾會等它執行完這個方法。(也就是這個對象可能在執行finalize()方法時被回收了),如果在finalize()方法中,對象加入了任何一個引用鏈中,則這個對象在這次回收器工作時就不會被回收了。

  在Java中,有幾種可作為GC Roots對象:虛擬機棧(棧幀中的本地變量表)中引用對象。方法區中類靜態屬性和常量引用的對象和本地方法棧中JNI引用的對象;

2.2 垃圾回收算法

2.2.1 標記-清除(Mark-Sweep)算法

首先會利用前面的可達性分析算法標記出需要回收的對象,在標記完成后就統一回收所有被標記的對象,這個算法的缺點主要有:

  • 效率問題,在標記和清除兩個過程中效率都不高;
  • 空間問題,標記清除之后會產生大量的內存碎片,碎片太多,可能導致在下次為大對象分配內存時,提前觸發一次垃圾回收動作;

2.2.2 復制算法(Coping)

將可用的內存分為兩塊,每次只使用其中的一塊,這樣每次只需要順序分配內存就可以,當一塊的內存用完后,就把還存活的對象復制到另一塊內存中去,然后對使用過的內存空間進行回收就可以了。(一般不會采用平均分成兩塊的方式,現代虛擬機一般會將內存分成一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和一塊Survivor空間,回收時,將Eden空間和Survivors空間里還存活的對象復制到另一塊沒有使用的Survivor空間中,然后清理掉用過的空間),一般會這種算法回收新生代的內存空間;

2.2.3 標記-整理(Mark-Compact)算法

先利用可達性分析算法標記需要回收的對象,然后就讓還存活的對象(出現在任何引用鏈中的對象)都向一端移動,然后清理掉端邊界外的內存。(一般用來回收老年代的對象);

3 什么時候回收

大多數情況下,對象優先在Eden區中分配(大對象直接在老年代分配),當Eden沒有足夠空間時,JVM就會發起一次Minor GC。在進行Minor GC 之前,JVM會檢查老年代最大可用的空間是否大於新生代所有對象的空間,如果成立,則Minor GC是安全的,否則,JVM就會去檢查HandlePromotionFailure設置值是否允許擔保失敗。如果允許擔保失敗,則會繼續檢查老年代最大可用空間是否大於歷次晉升到老年代對象的平均大小,如果是,則會嘗試進行Minor GC(若失敗,就會進行一次Full GC);否則就會改為進行一次Full GC;

 


免責聲明!

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



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