垃圾收集器與內存分配策略

提及垃圾收集(Garbage Collection,GC),不得不思考GC須要完成的3件事情: java

哪些內存須要回收? 
什麼時間回收? 
如何回收?
算法

程序計算器、虛擬機棧、本地方法棧3個區域隨線程而生,隨線程而滅;棧中的棧幀隨着方法的進入和退出而有條不紊地執行着出棧和入棧操做。每個棧幀中分配多少內存基本上是在類結構肯定下來時就已知的(不考慮JIT優化的狀況下),因此這幾個區域的內存分配和回收都具有肯定性,在這幾個區域就不須要過多考慮回收的問題,由於方法結束或者線程結束時,內存天然就跟着回收了。而Java堆和方法區則不同,一個接口的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,只有在程序運行期才能知道建立哪些對象,這部份內存的分配和回收都是動態的,垃圾收集器所關注的就是這部份內存。數組

對象已死嗎 

1. 引用計數算法(Reference Counting) 

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器的值就加1;當引用失效時,計數器的值就減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。 
優勢:實現簡單,斷定效率很高。 
缺點:不能解決對象之間互相循環引用的問題。
緩存

 1 /**
 2  * Created by sakura on 2017/12/9.
 3  */
 4 public class ReferenceCountingGC {
 5     public Object instance=null;
 6     private static final int _1MB=1024*1024;
 7     /*
 8     這個成員屬性的惟一意義就是佔點內存,以便在GC日誌中看清楚是否被回收過
 9      */
10     private byte[] bigSize=new byte[2*_1MB];
11     public static void main(String[] args) {
12         ReferenceCountingGC objA=new ReferenceCountingGC();
13         ReferenceCountingGC objB=new ReferenceCountingGC();
14         objA.instance=objB;
15         objB.instance=objA;
16         objA=null;
17         objB=null;
18         System.gc();
19     }
20 }

GC日誌中的7437K->848K,意味着虛擬機並無由於這兩個對象互相引用就不回收它們,這也側面說明了虛擬機並非經過引用計數算法來判斷對象是否存活的。安全

2. 可達性分析算法 (Reachability Analysis) 

經過一系列的GC Roots的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。 服務器

在Java語言中,可做爲GC Roots的對象包括下面幾種: 數據結構

虛擬機棧(棧幀中的本地向量表)中引用的對象。 多線程

方法區中類靜態屬性引用的對象。 併發

方法區中常量引用的對象。 ide

本地方法棧中JNI(即通常說的Native方法)引用的對象。

3. 再談引用 

JDK1.2以前,Java中引用的定義很傳統:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。這種定義很純粹,但不夠細緻。在JDK1.2以後,Java對引用的概念進行擴充,引用被分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。

強引用:只要某個對象有強引用與之關聯,JVM一定不會回收這個對象,即便在內存不足的狀況下,JVM寧願拋出OutOfMemory錯誤也不會回收這種對象。

1 Object object = new Object();
2 String str = "hello";

軟引用:用來描述一些有用但並非必需的對象,在Java中用java.lang.ref.SoftReference類來表示且只有在內存不足時JVM纔會回收被軟引用關聯的對象。這個特性比較適合實現緩存

 1 import java.lang.ref.SoftReference;
 2 /**
 3  * Created by sakura on 2017/12/9.
 4  */
 5 public class SoftRef {
 6     public static void main(String[] args) {
 7         SoftReference<String> str=new SoftReference<String>("hello");
 8         System.out.println(str.get());//hello
 9         System.gc();
10         System.out.println(str.get());//hello
11     }
12 }

弱引用:也是用來描述非必需對象的,可是它的強度比軟引用更弱一些,被軟引用關聯的對象只能生存到下一次垃圾收集發生以前。當JVM進行垃圾回收時,不管內存是否充足,都會回收被弱引用關聯的對象。在JDK1.2以後提供了WeakReference類來實現弱引用。弱引用能用來在回調函數中防止內存泄露

 1 import java.lang.ref.WeakReference;
 2 /**
 3  * Created by sakura on 2017/12/9.
 4  */
 5 public class WeakRef {
 6     public static void main(String[] args) {
 7         WeakReference<String> str=new WeakReference<String>("hello");
 8         System.out.println(str.get());
 9         System.gc();
10         System.out.println(str.get());
11     }
12 }

虛引用:也稱幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK1.2以後,提供了PhantomReference類來實現虛引用。

4. 生存仍是死亡 

即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經理兩次標記過程:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那它會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機把這兩種狀況都視爲「沒有必要執行」。若是這個對象被斷定爲有必要執行finalize()方法,那麼這個對象會被放置在一個叫F-Queue的隊列之中,並在稍後由一個由虛擬機自動創建的、低優先級的Finalizer線程去執行它。這裏所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束(防止發生死循環)。finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC會對F-Queue中的對象進行第二次標記,若是對象在finalize()方法中成功拯救本身——只要從新和引用鏈上的任何一個對象創建關聯便可,那麼在第二次標記時它會被移除出「即將回收」的集合,若是對象這個時間點尚未逃脫,那基本上它就真的被回收了。

 1 /**
 2  * Created by sakura on 2017/12/9.
 3  */
 4 /*
 5 1. 對象能夠在被GC時自我拯救
 6 2. 這種自救的機會只有一次,由於一個對象的finalize()方法最多隻會被系統調用一次
 7  */
 8 public class FinalizeEscapeGC {
 9     public static FinalizeEscapeGC SAVE_HOOK=null;
10     public void isAlive(){
11         System.out.println("yes, i am still alive :)");
12     }
13     @Override
14     protected void finalize() throws Throwable{
15         super.finalize();
16         System.out.println("finalize method executed!");
17         FinalizeEscapeGC.SAVE_HOOK=this;
18     }
19     public static void main(String[] args) throws InterruptedException {
20         SAVE_HOOK =new FinalizeEscapeGC();
21         //對象第一次成功拯救本身
22         SAVE_HOOK=null;
23         System.gc();
24         //由於finalize方法優先級很低,因此暫停0.5秒以等待它
25         Thread.sleep(500);
26         if(SAVE_HOOK!=null)
27             SAVE_HOOK.isAlive();
28         else
29             System.out.println("no, i am dead :(");
30         //下面這段代碼與上面的徹底相同,可是此次自救卻失敗了
31         SAVE_HOOK=null;
32         System.gc();
33         //由於finalize方法優先級很低,因此暫停0.5秒以等待它
34         Thread.sleep(500);
35         if(SAVE_HOOK!=null)
36             SAVE_HOOK.isAlive();
37         else
38             System.out.println("no, i am dead :(");
39     }
40 }
41 /*
42 finalize method executed!
43 yes, i am still alive :)
44 no, i am dead :(
45  */

5. 回收方法區 

永久代的垃圾收集主要回收兩部份內容:廢棄常量和無用的類。回收廢棄常量與回收Java堆中的對象很是相似。以常量池中字面量的回收爲例,假如一個字符創「abc」已經進入了常量池中,可是當前系統沒有任何一個String對象叫「abc」的,換句話說,就是沒有任何String對象引用常量池中的「abc」對象,也沒有其餘地方引用了這個字面量,若是這時發生GC且有必要的話,這個「abc」常量就會被系統清理出常量池。常量池中的其餘類(接口)、方法、字段的符號引用也與此相似。斷定一個常量是不是「廢棄常量」比較簡單,而要斷定一個類是不是「無用的類」的條件則相對苛刻不少,對「無用的類」的斷定須要同時知足下面3個條件: 
此類全部的實例都已經被回收,也就是Java堆中不存在此類的任何實例。 
加載此類的ClassLoader已經被回收。 
此類對應的java.lang.Class對象沒有在任何地方被引用,沒法再任何地方經過反射訪問到此類的方法。 
虛擬機能夠對知足上述3個條件的無用類進行回收,這裏說的僅僅是「能夠」,而並非和對象同樣,不使用了就必然會回收。

垃圾收集算法

1. 標記-清除算法(Mark-Sweep) 

標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。這是最基礎的收集算法,後續的收集算法都是基於這種思路並對其不足進行改進而獲得的。它的不足主要有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另外一個是空間問題,標記清除後會產生大量不連續的內存碎片,致使後續內存分配時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。 

 

2. 複製算法(Copying) 

爲了解決效率問題,一種稱爲「複製」的收集算法出現了,它把可用內存按容量劃分爲大小的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就把還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。 
優勢:不用考慮內存碎片的狀況,實現簡單,運行高效。 
缺點:把內存縮小爲原來的一半,代價有點高。 

如今的商業虛擬機都採用這種收集算法來回收新生代,研究代表,新生代中的對象98%是「朝生夕死」的,因此並不須要按照1:1的比例來劃份內存空間,而是把內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,把Eden和Survivor中還存活的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例爲8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90%,只有10%的內存會被「浪費」。固然,98%的對象可回收只是通常場景下的數據,咱們沒有辦法保證每次回收都只有很少於10%的對象存活,當Survivor空間不夠用時,須要依賴其餘內存(老年代)進行分配擔保(Handle Promotion)。

3. 標記-整理算法(Mark-Compact) 

複製收集算法在對象存活率較高時就要進行較多的複製操做,性能會變低。更關鍵的是,若是不想浪費50%的空間,就須要有額外的空間進行分配擔保,以應對被使用的內存中全部對象都100%存活的極端狀況,全部在老年代通常不能直接選用這種算法。有人提出另外一種「標記-整理」算法,標記過程仍然和「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。 

4. 分代收集算法(Generational Collection)

根據對象存活週期的不一樣把內存分爲幾塊。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率高且沒有額外空間對它進行分配擔保,就必須使用標記-清理或者標記-整理算法來進行回收。

HotSpot的算法實現 

1. 枚舉根節點 

可達性分析的時間停頓,由於這項分析工做必須在一個能確保一致性的快照中進行,這裏一致性的意思是指在整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不能夠出現分析過程當中對象引用關係還在不斷變化的狀況。這點是致使GC進行時必須停頓全部Java執行線程(Stop The World)的其中一個重要緣由。在執行系統停下來後,並不須要一個不漏地檢查完全部執行上下文和全局的引用位置,虛擬機經過OopMap數據結構直接得知哪些地方存放着對象引用。

2. 安全點

HotSpot沒有爲每條指令都生成OopMap,只是在特定位置記錄了這些信息,這些位置被稱爲安全點(Safepoint),即程序執行時並不是在全部地方都能停頓下來GC,只有在到達安全點時才能暫停。

3. 安全區域

線程處於Sleep狀態或者Blocked狀態時,沒法響應JVM的中斷請求,走到安全的地方去中斷掛起,對於這種狀況,就須要安全區域(Safe Region)來解決。 安全區域是指在一段代碼片斷之中,引用關係不會發生變化,在這個區域中的任意地方開始GC都是安全的。

垃圾收集器

若是說收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。 

HotSpot虛擬機的垃圾收集器

1. Serial收集器 

最基本、發展歷史最悠久的收集器。是一個單線程的收集器,這裏的單線程並不只僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工做,更重要的是它在垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。 

Serial是虛擬機運行在Client模式下的默認新生代收集器。它的優勢是簡單而高效(與其餘收集器的單線程比)。

2. ParNew收集器 

其實就是Serial收集器的多線程版本。除了使用多條線程進行垃圾收集以外,其他行爲包括Serial收集器可用的全部控制參數、收集算法、Stop The World、對象分配規則、回收策略等都與Serial收集器徹底同樣。 

ParNew是虛擬機運行在Server模式下的首選新生代收集器。一個與性能無關的緣由是除了Serial收集器外,目前只有它能與CMS收集器配合工做。CMS收集器是HotSpot虛擬機中第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做。 
注意並行與併發是兩個容易混淆的概念: 
並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態。 
併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。

3. Parallel Scavenge收集器 

是一個新生代收集器,也是使用複製算法且並行的多線程收集器。Parallel Scavenge收集器的特色是它的關注點與其餘收集器不一樣,CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput),所謂吞吐量就是CPU用於運行用戶代碼的時間和CPU總消耗時間的比值。停頓時間越短就越適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗,而高吞吐量則能夠高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。

4. Serial Old收集器 

是Serial收集器的老年代版本。主要有兩大用途:一種用途是在JDK1.5以前與Parallel Scavenge收集器搭配使用,另外一種用途是做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。 

5. Parallel Old收集器 

是Parallel Scavenge收集器的老年代版本。使用多線程和標記-整理算法,這個收集器是在JDK1.6中才開始提供的。 

6. CMS收集器 

即Concurrent Mark Sweep收集器,是一種以獲取最短回收停頓時間爲目標的收集器。能夠提升服務的響應速度,帶給用戶較好的體驗。CMS是基於標記-清除算法實現的,運行過程分爲4個步驟: 
初始標記(CMS initial mark) 
併發標記(CMS concurrent mark) 
從新標記(CMS remark) 
併發清除(CMS concurrent sweep) 
其中初始標記、從新標記這兩個步驟仍須要STW始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而從新標記階段則是爲了修正併發標記期間因用戶程序繼續尋作而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。 

優勢:併發收集、低停頓。 
缺點: 
a. CMS收集器對CPU資源很是敏感。在併發階段,它雖然不會致使用戶線程停頓,可是會由於佔用了一部分線程(或者說CPU資源)而致使應用程序變慢,總吞吐量會下降。 
b. CMS收集器沒法處理動垃圾(Floating Garbage),可能出現Concurrent Mode Failure失敗而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行着,伴隨程序運行天然就還會有新的垃圾不斷產生,只好留待下一次GC時再清理掉。這一部分垃圾就成爲浮動垃圾。也是因爲在垃圾收集階段用戶線程還須要運行,那就還須要預留有足夠的內存空間給用戶線程使用,因此CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集。 
c. 標記-清除算法會產生大量的內存碎片,會給接下來的內存分配帶來麻煩,每每會出現老年代還有很大的空間剩餘,可是沒法找到足夠大的連續空間來分配當前對象,不得不提早觸發一次Full GC。

7. G1收集器 

即Garbage-First收集器,是一款面向服務器端的垃圾收集器,具備如下特色: 
並行與併發:充分利用多核優點縮短STW停頓的時間,部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓Java程序繼續執行。 
分代收集:採用不一樣的方式去處理新建立的對象和已經存活了一段時間、熬過屢次GC的舊對象以獲取更好的收集效果。 
空間整合:與CMS的標記-清理算法不一樣,G1從總體上看是基於標記-整理算法實現的收集器,從局部(兩個Region之間)上來看是基於複製算法實現的,但不管如何,這兩種算法都意味着G1運做期間不會產生內存空間碎片。 
可預測的停頓:G1除了追求低停頓外,還能創建可預測的停頓時間模型,這幾乎已是實時Java(RTSJ)的垃圾收集器的特徵了。 
G1收集器的運做大體可劃分爲如下幾個步驟(不考慮維護Remembered Set的操做): 
初始標記(Initial Marking) 
併發標記(Concurrent Marking) 
最終標記(Final Marking) 
篩選回收(Live Data Counting and Evacuation) 
初始標記階段僅僅只是標記如下GC Roots能直接關聯到的對象,而且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中建立新對象,這階段須要停頓線程,但耗時很短併發標記階段是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時很長,但可與用戶程序併發執行。而最終標記階段則是爲了修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機把這段時間對象變化記錄在線程Remembered Set Logs裏面,最終標記階段須要把Remembered Set Logs的數據合併到Remembered Set中,這階段須要停頓線程,可是可並行執行。最後在篩選回收階段先對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃(這個階段也能夠與用戶線程一塊兒併發執行)。 

8. GC日誌 

內存分配與回收策略 

Java的自動內存管理最終能夠歸結爲自動化地解決了兩個問題:給對象分配內存以及回收分配給對象的內存。

1. 對象優先在Eden分配 

大多數狀況下,對象在新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機會發起一次Minor 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倍以上。

 1 /**
 2  * Created by sakura on 2018/3/5.
 3  */
 4 /*
 5 VM Args:-verbose:gc -Xms20M -Xmx20M -Xmn10M  -XX:+PrintGCDetails -XX:SurvivorRatio=8
 6  */
 7 public class Demo {
 8     private static final int _1MB=1024*1024;
 9     public static void main(String[] args) {
10         byte[] allocation1,allocation2,allocation3,allocation4;
11         allocation1=new byte[2*_1MB];
12         allocation2=new byte[2*_1MB];
13         allocation3=new byte[2*_1MB];
14         allocation4=new byte[4*_1MB];//出現一次Minor GC
15     }
16 }
17 /*
18 運行時經過-Xms20M -Xmx20M -Xmn10M這3個參數限制了Java堆大小爲20MB,不可擴展,其中10MB分配會新生代,剩下的10MB分配給老年代。
19 -XX:SurvivorRatio=8決定了新生代中Eden區與一個Survivor區的空間比例是8:1,從輸出結果也能夠清晰地看到"eden space 8192K、from space 1024K、
20 to   space 1024K"的信息,新生代可用空間爲9216KB(Eden區+1個Survivor區的總容量)。執行allocation4=new byte[4*_1MB];時會發生一次Minor GC,
21 此次GC的結果是新生代6298KB變爲904KB,而總內存佔用量則幾乎沒有減小(由於allocation一、allocation二、allocation3三個對象都是存活的,虛擬機幾乎
22 沒有找到可回收的對象)。此次GC發生的緣由是給allocation4分配內存時,發現Eden已經被佔用了6MB,剩餘空間已不足以分配allocation4所需的4MB內存,所以
23 發生Minor GC。GC期間虛擬機又發現已有的3個2MB大小的對象所有沒法放入Survivor空間(Survivor空間只有1MB大小),因此只好經過分配擔保機制提早轉移到老
24 年代去。此次GC結束後,4MB的allocation4對象順利分配在Eden中,所以程序執行完的結果是Eden空間佔用4MB(被allocation4佔用),Survivor空閒,老年代被
25 佔用6MB(被allocation一、allocation二、allocation3佔用)。
26  */
27 /*
28 [GC (Allocation Failure) [PSYoungGen: 6298K->904K(9216K)] 6298K->5008K(19456K), 0.0334379 secs] [Times: user=0.00 sys=0.00, real=0.04 secs] 
29 Heap
30  PSYoungGen      total 9216K, used 7369K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
31   eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc50640,0x00000000ffe00000)
32   from space 1024K, 88% used [0x00000000ffe00000,0x00000000ffee2020,0x00000000fff00000)
33   to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
34  ParOldGen       total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
35   object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
36  Metaspace       used 3485K, capacity 4502K, committed 4864K, reserved 1056768K
37   class space    used 387K, capacity 390K, committed 512K, reserved 1048576K
38  */

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

所謂的大對象是指,須要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。一羣朝生夕滅的短命大對象對Java虛擬機的內存分配來講是一個壞消息,常常出現大對象容易致使內存還有很多空間時就提早觸發垃圾收集以獲取足夠的連續空間來安置它們。虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配,這樣能夠避免在Eden區及兩個Survivor區之間發生大量的內存複製。

 1 /**
 2  * Created by sakura on 2018/3/5.
 3  */
 4 /*
 5 VM Args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails 
 6 -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
 7  */
 8 public class Demo2 {
 9     private static final int _1MB=1024*1024;
10     public static void main(String[] args) {
11         byte[] allocation;
12         allocation=new byte[4*_1MB];//直接分配在老年代
13     }
14 }
15 /*
16 咱們看到Eden空間幾乎沒有被使用,而老年代的10MB空間被使用了40%,也就是4MB的allocation對象就直接分配在老年代中,這是由於
17 PretenureSizeThreshold被設置爲3145728,所以超過3MB的對象都會直接在老年代進行分配。
18  */
19 /*
20 Heap
21  PSYoungGen      total 9216K, used 6462K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
22   eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc4fa70,0x00000000ffe00000)
23   from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
24   to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
25  ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
26   object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
27  Metaspace       used 3412K, capacity 4500K, committed 4864K, reserved 1056768K
28   class space    used 379K, capacity 388K, committed 512K, reserved 1048576K
29  */

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

虛擬機給每一個對象定義了一個對象年齡(Age)計數器,若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,會被移動到Survivor空間中,而且對象的年齡設爲1。對象在Survivor區中每熬過一次Minor GC年齡就增長1歲,當它的年齡增長到必定程度(默認爲15歲),就會被晉升到老年代中。對象晉升老年代的年齡閾值,能夠經過參數-XX:MaxTenuringThreshold設置。

 1 /**
 2  * Created by sakura on 2018/3/5.
 3  */
 4 /*
 5 VM Args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
 6 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
 7  */
 8 public class Demo3 {
 9     private static final int _1MB=1024*1024;
10     public static void main(String[] args) {
11         byte[] allocation1,allocation2,allocation3;
12         allocation1=new byte[_1MB/4];
13         allocation2=new byte[4*_1MB];
14         allocation3=new byte[4*_1MB];
15         allocation3=null;
16         allocation3=new byte[4*_1MB];
17     }
18 }
19 /*
20 -XX:MaxTenuringThreshold=1
21 Heap
22  PSYoungGen      total 9216K, used 6718K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
23   eden space 8192K, 82% used [0x00000000ff600000,0x00000000ffc8fa80,0x00000000ffe00000)
24   from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
25   to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
26  ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
27   object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400020,0x00000000ff600000)
28  Metaspace       used 3475K, capacity 4500K, committed 4864K, reserved 1056768K
29   class space    used 385K, capacity 388K, committed 512K, reserved 1048576K
30 -XX:MaxTenuringThreshold=15
31 Heap
32  PSYoungGen      total 9216K, used 6718K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
33   eden space 8192K, 82% used [0x00000000ff600000,0x00000000ffc8fa80,0x00000000ffe00000)
34   from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
35   to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
36  ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
37   object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400020,0x00000000ff600000)
38  Metaspace       used 3476K, capacity 4500K, committed 4864K, reserved 1056768K
39   class space    used 385K, capacity 388K, committed 512K, reserved 1048576K
40  */

4. 動態對象年齡斷定

虛擬機並非永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於此年齡的對象就能夠直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

 1 /**
 2  * Created by sakura on 2018/3/6.
 3  */
 4 /*
 5 VM Args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
 6 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
 7  */
 8 public class Demo4 {
 9     private static final int _1MB=1024*1024;
10     public static void main(String[] args) {
11         byte[] allocation1,allocation2,allocation3,allocation4;
12         allocation1=new byte[_1MB/4];
13         allocation2=new byte[_1MB/4];
14         allocation3=new byte[4*_1MB];
15         allocation4=new byte[4*_1MB];
16         allocation4=null;
17         allocation4=new byte[4*_1MB];
18     }
19 }
20 /*
21 allocation一、allocation2對象都直接進入了老年代,而沒有等到15歲的臨界年齡
22  */
23 /*
24 Heap
25  PSYoungGen      total 9216K, used 6974K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
26   eden space 8192K, 85% used [0x00000000ff600000,0x00000000ffccfa90,0x00000000ffe00000)
27   from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
28   to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
29  ParOldGen       total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
30   object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400020,0x00000000ff600000)
31  Metaspace       used 3475K, capacity 4500K, committed 4864K, reserved 1056768K
32   class space    used 385K, capacity 388K, committed 512K, reserved 1048576K
33  */

5. 空間分配擔保

在發生Minor GC以前,虛擬機會檢查老年代最大可用的連續空間是否大於新生代全部對象的總空間,若是這個條件成立,那麼Minor GC能夠確保是安全的,若是不成立,則虛擬機會查看HandlePromotionFailure設置值是否容許擔保失敗。若是容許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於,會嘗試着進行一次Minor GC,儘管這一次Minor GC是有風險的;若是小於,或者HandlePromotionFailure設置不容許冒險,那這時也要改成進行一次Full GC(JDK6 Update 24以後的規則變爲只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,不然會進行Full GC)。

相關文章
相關標籤/搜索