一,JVM內存結構:在JVM內存結構中,大致(為什麼是大致?因為虛擬機的種類繁多,有些虛擬機把棧概念合二為一,還有其他自由的實現)會分為如下的結構:

包括本地方法棧,VM棧,程序計數器,方法區,java堆,下面逐一來看下他們的功能:

①本地方法棧:放著大量虛擬機可直接調用的native方法,比如CAS模型中大量使用的Unsafe包中的方法,基本都是native方法,這些方法很多並不是用java實現的,而是C,C++等;但是可供java直接調用;

②,vm棧:存放線程執行方法時產生的棧幀,通常一個線程會有一個棧幀鏈,如下圖所示,一個線程正在執行的棧幀只會是一個當前棧幀,棧幀中包含的數據結構包括:局部變數表(方法中的局部變數)、操作數棧(運算過程中的中間存儲媒介)、動態鏈接、方法返回地址和一些額外的附加信息,如下圖:

③,程序計數器:每個線程記錄指令執行到的位置,通過程序計數器控制諸如循環,異常處理等的下一條指令;

④,方法區:存放已經被虛擬機載入的類信息,常量,靜態變數,即時編譯器編譯後的代碼等數據,還有運行時常量,通常稱為永久代,通常情況不會進行GC;

⑤,java堆:絕大多數實例對象都在此存放;

換個圖來看java內存模型可知:方法區和堆是線程共享的,其他的區域是線程私有的;

二,JVM GC:

1),對象是否能回收的判斷:

(1),不可達性對象可以回收;

(2),對於用可達性分析法搜索不到的對象,GC並不一定會回收該對象。要完全回收一個對象,至少需要經過兩次標記的過程:

第一次標記:對於一個沒有其他引用的對象,篩選該對象是否有必要執行finalize()方法,如果沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;因為finalize方法只能被執行一次)。

第二次標記:如果被篩選判定位有必要執行,則會放入FQueue隊列,並自動創建一個低優先順序的finalize線程來執行釋放操作。如果在一個對象釋放前被其他對象引用,則該對象會被移除FQueue隊列。

根搜索演算法:JVM選定諸如方法區的靜態常量,本地方法中的對象等作為GC roots(對象可達樹的根節點),將創建的所有的對象引用掛在樹上,當對象引用從樹上解掛時(沒有對象再引用這個對象時),則這個對象處於不可達狀態,也即是可回收狀態;如下圖:

2)JVM內存分區和GC演算法

內存被切分為三塊:新生代(剛new出來的對象),老年代(從新生代GC過來的對象或者剛new出來的大對象(直接超過了新生代的空閑內存)),永久代(方法區數據)

新生代又被分為一塊Eden區和兩塊Survivor區;

新生代GC演算法:複製演算法採用的方式為從根集合進行掃描,將存活的對象移動到一塊空閑的區域

標記-清除:該演算法採用的方式是從跟集合開始掃描,對存活的對象進行標記,標記完畢後,再掃描整個空間中未被標記的對象,並進行清除。標記和清除的過程如下:

上圖中藍色部分是有被引用的對象,褐色部分是沒有被引用的對象。在Marking階段,需要進行全盤掃描,這個過程是比較耗時的。

清除階段清理的是沒有被引用的對象,存活的對象被保留。

標記-清除動作不需要移動對象,且僅對不存活的對象進行清理,在空間中存活對象較多的時候,效率較高,但由於只是清除,沒有重新整理,因此會造成內存碎片。

標記-壓縮:該演算法與標記-清除演算法類似,都是先對存活的對象進行標記,但是在清除後會把活的對象向左端空閑空間移動,然後再更新其引用對象的指針,如下圖所示

由於進行了移動規整動作,該演算法避免了標記-清除的碎片問題,但由於需要進行移動,因此成本也增加了。(該演算法適用於舊生代)

3),虛擬機中GC的過程:

1,在初始階段,新創建的對象被分配到Eden區,survivor的兩塊空間都為空。

2,當Eden區滿了的時候,minor garbage 被觸發 。

3,經過掃描與標記,存活的對象被複制到S0,不存活的對象被回收

4,在下一次的Minor GC中,Eden區的情況和上面一致,沒有引用的對象被回收,存活的對象被複制到survivor區。然而在survivor區,S0的所有的數據都被複制到S1,需要注意的是,在上次minor GC過程中移動到S0中的兩個對象在複製到S1後其年齡要加1。此時Eden區S0區被清空,所有存活的數據都複製到了S1區,並且S1區存在著年齡不一樣的對象,過程如下圖所示:

5,再下一次MinorGC則重複這個過程,這一次survivor的兩個區對換,存活的對象被複制到S0,存活的對象年齡加1,Eden區和另一個survivor區被清空。

6,再經過幾次Minor GC之後,當存活對象的年齡達到一個閾值之後(可通過參數配置,默認是8),就會被從年輕代Promotion到老年代。

7,隨著MinorGC一次又一次的進行,不斷會有新的對象被promote到老年代。

8,上面基本上覆蓋了整個年輕代所有的回收過程。最終,MajorGC將會在老年代發生,老年代的空間將會被清除和壓縮。

4),Minor GC,Major GC,Full GC觸發條件

Minor GC:當年輕代中的Eden區滿時,觸發;

Major GC:清理老年代,通常由Minor GC觸發;

Full GC:

(1)調用System.gc時,建議執行full GC,但是不一定執行;

(2)老年代空間不足,

(3)方法區空間不足,

(4)通過Minor GC進入老年代的平均大小大於老年代的可用內存;

(5)Minor GC觸發Full GC:新生代的eden區和suvivor(使用中)向survivor(暫未使用)複製對象的時候,大於survivor(暫未使用)的內存,隨即把對象轉存到老年代,但同樣大於老年代的可用永存

5),JVM參數與調優:

JVM參數:

-Xmx:最大允許分配堆內存;

-Xms:初始分配的堆內存; 通常與Xmx一樣,避免每次GC後重新分配內存

CMSFullGCsBeforeCompaction=5:會每隔5次真正的full GC做一次壓縮

-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對內存佔用率達到70%的時候開始GC(因為CMS會有浮動垃圾,所以一般都較早啟動GC);

-XX:+CMSParallelRemarkEnabled 減少第二次暫停的時間,開啟並行的remark;

-XX:+DisableExplicitGC 禁止代碼中顯式的調用GC,

-XX:+DoEscapeAnalysis 打開逃逸(例如被靜態變數引用等導致無法回收)分析

XX:+UseCMSCompactAtFullCollection 開啟碎片合併

XX:+UseConcMarkSweepG 使用CMS收集器

-XX:+UseParNewGC 年輕代為多線程收集。

還有更多的JAVA乾貨技術分享,敬請關注。。。


推薦閱讀:
相关文章