從引用說起
Object object = new Object();
- 假設這句代碼出現在方法體中,"Object object” 這部分將會反映到Java棧的本地變量中,作為一個reference類型數據出現。
- “new Object()”這部分將會反映到Java堆中,形成一塊存儲Object類型所有實例數據值的結構化內存,根據具體類型以及虛擬機實現的對象內存布局的不同,這塊內存的長度是不固定。
- 另外,在java堆中還必須包括能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些數據類型存儲在方法區中。
- reference類型在java虛擬機規范里面只規定了一個
指向對象的引用地址
,並沒有定義這個引用應該通過那種方式去定位,訪問到java堆中的對象位置,因此不同的虛擬機實現的訪問方式可能不同,主流的方式有兩種:使用句柄和直接指針。
指針直接引用
reference變量中直接存儲的就是對象的地址,而java堆對象一部分存儲了對象實例數據,另外一部分存儲了對象類型數據。
句柄引用
java堆中將划分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息。
優缺點
這兩種訪問對象的方式各有優勢,使用句柄訪問方式最大好處就是reference中存儲的是穩定的句柄地址,在對象移動時只需要改變句柄中的實例數據指針,而reference不需要改變。使用指針訪問方式最大好處就是速度快,它節省了一次指針定位的時間開銷,就虛擬機而言,它使用的是第二種方式(直接指針訪問)
如何判斷對象死亡
堆中放着幾乎所有的對象實例,回收前第一步就要判斷對象是否死亡
引用計數法
給對象增加一個引用計數器,每當有一個地方引用他,計數器就加1:當引用失效后,計數器就減1.任何時候計數器為0的對象就是可回收對象。
- 優點:判定效率很高
- 缺點:不會完全准確,因為如果出現兩個對象相互引用的問題就不行了
/**
* testGC()方法執行后會不會被GC? 不會!!!!
*
* @author TongWei.Chen 2017-09-05 11:15:53
*/
public class ReferenceCountingGC {
public Object instance = null;
public static void testGC() {
//step 1
ReferenceCountingGC objA = new ReferenceCountingGC();
//step 2
ReferenceCountingGC objB = new ReferenceCountingGC();
//相互引用
//step 3
objA.instance = objB;
//step 4
objB.instance = objA;
//step 5
objA = null;
//step 6
objB = null;
//假設在這行發生CG,objA和objB是否能被回收? 不能!!!!
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
可達性分析法
通過一系列稱為“GC Roots”的對象作為七點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈,當一個對象到GCRoots沒有任何引用鏈相鏈時,則證明此對象是不可用的。
可以作為GC Roots的對象包括以下幾點
1、虛擬機棧(棧幀中的本地變量表)中引用的對象。
2、方法區中的類靜態屬性引用的對象或者常量引用的對象。
3、本地方法棧中JNI(就是native方法)引用的對象。
垃圾收集算法
標記-清除算法
最基礎的收集算法是“標記-清除”(Mark-Sweep)算法,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象。
標記-清除算法是最基礎的收集算法,其他的收集算法都是基於這種思路並對其不足進行改進而得到的。
- 不足:
- 效率問題,標記和清除兩個過程的效率都不高;
- 空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
復制算法
將可用內存按容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
- 不足:這種算法的代價是將內存縮小為了原來的一半,未免太高了一點。
復制算法——優化
- 現在的商業虛擬機都采用這種收集算法來回收新生代
- 新生代中的對象98%是“朝生夕死”的,所以並不需要按照1:1的比例來划分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。
當回收時,將Eden和Survivor中還存活着的對象一次性地復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。
-
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是每次新生代中可用內存空間為整個新生代容量的90%(80%+10%),只有10%的內存會被“浪費”。
-
98%的對象可回收只是一般場景下的數據,沒有辦法保證每次回收都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這里指老年代)進行分配擔保(Handle Promotion)
有關年輕代的JVM參數
- -XX:NewSize和-XX:MaxNewSize
用於設置年輕代的大小,建議設為整個堆大小的1/3或者1/4,兩個值設為一樣大。
- -XX:SurvivorRatio
用於設置Eden和其中一個Survivor的比值,這個值也比較重要。
- -XX:+PrintTenuringDistribution
這個參數用於顯示每次Minor GC時Survivor區中各個年齡段的對象的大小。
- -XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用於設置晉升到老年代的對象年齡的最小值和最大值,每個對象在堅持過一次Minor GC之后,年齡就加1。
標記-整理算法
復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
- 標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
分代收集算法
上面在講復制算法優化的時候也提到了年輕代使用的垃圾收集算法。
- 當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,根據對象存活周期的不同將內存划分為幾塊。
- 把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。
幾種常見的垃圾回收器
串行:Serial 和Serial Old組合收集
- 串行垃圾回收器在進行垃圾回收時,它會持有所有應用程序的線程,凍結所有應用程序線程,使用單個垃圾回收線程來進行垃圾回收工作。
- 串行垃圾回收器是為單線程環境而設計的,如果你的程序不需要多線程,啟動串行垃圾回收。
- 串行收集器是最古老,最穩定以及高效的收集器,可能會產生較長的停頓,只使用一個線程去回收。新生代、老年代使用串行回收;新生代復制算法、老年代標記-壓縮;垃圾收集的過程中會Stop The World(服務暫停)
- 使用方法:-XX:+UseSerialGC 串聯收集
串行:ParNew收集器+Serial Old組合收集
- ParNew收集器其實就是Serial收集器的多線程版本。新生代並行,老年代串行;新生代復制算法、老年代標記-壓縮
- ParNew是並行收集器,不是並發收集器。並行收集器只是串行的多線程版本而已,此時用戶線程仍然處於等待狀態。並發是指用戶線程和垃圾收集線程可以同時執行,在不通的cpu上。
- 使用方法:-XX:+UseParNewGC ParNew收集器
並行:Parallel Scavenge收集器+Serial Old(ps marksweep)組合收集
- 跟ParNew類似,是一種吞吐量優先收集器,即目標是達到一個可控制的吞吐量
吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間) + 垃圾收集時間
- 新生代復制算法、老年代標記-壓縮
- 使用方法:-XX:+UseParallelGC Parallel Scavenge收集器
並行:Parallel Scavenge收集器+Parallel Old組合收集
- Parallel Old是Parallel Scavenge收集器的老年代版本
- 使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供
- 使用方法:-XX:+UseParallelOldGC Parallel Scavenge Old收集器
並發:ParNew+標記掃描CMS(Concurrent Mark Sweep)+Serial Old收集器
- 以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。
- CMS收集器是基於“標記-清除”算法實現的
- 4個步驟,包括:
- 初始標記(CMS initial mark):暫停所有其他線程,並記錄下直接和roots相連的對象,速度很快;
- 並發標記(CMS concurrent mark):同時開啟GC和用戶線程,用一個閉包結構去記錄可達對象。但在這個階段結束,這個閉包結構並不能保證包含當前所有的可達對象,因為用戶線程可能會不斷的更新引用域,所以GC無法保證可達對象分析的實時性。所以這個算法里會跟蹤記錄這些發生引用更新的地方。
- 重新標記(CMS remark):就是為了修正並發標記期間因為用戶程序繼續運行而導致標記變動的那一部分對象,這個階段時間會比初始標記時間長,比並發標記時間短。
- 並發清除(CMS concurrent sweep):開啟用戶線程,同時GC線程開始對標記的區域進行清掃。
- Serial Old作為cms收集器出現Concurrent Mode Failure失敗后的后備收集器使用
- 使用方法:-XX:+UseConcMarkSweepGC cms收集器
- 優點:並發收集、低停頓。
- 缺點:
- 對cpu資源敏感
- 無法處理大量的浮動垃圾
- “標記清除算法”產生大量空間碎片、並發階段會降低吞吐量
-XX:+ UseCMSCompactAtFullCollection Full GC后,進行一次碎片整理;整理過程是獨占的,會引起停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設置進行幾次Full GC后,進行一次碎片整理
-XX:ParallelCMSThreads 設定CMS的線程數量(一般情況約等於可用CPU數量)
並發:G1收集器
G1是目前技術發展的最前沿成果之一,HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發布的CMS收集器。與CMS收集器相比G1收集器有以下特點:
- 空間整合:G1收集器采用標記整理算法,不會產生內存空間碎片。分配大對象時不會因為無法找到連續空間而提前觸發下一次GC。
- 可預測停頓:這是G1的另一大優勢,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特征了
- 分代收集:上面提到的垃圾收集器,收集的范圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內存布局與其他收集器有很大差別,它將整個Java堆划分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合。
- 可預測的停頓:這是G1相對CMS的另一大優勢,除了可以降低停頓時間外,還能建立可預測的停頓時間模型,讓使用者明確指定在一個為ms的時間片段內。
- G1還維護了一個優先隊列,每次根據允許的時間,優選選擇回收價值最大的Region(這也是G1名字的由來)。
- 收集步驟
- 標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),並且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
- 並發標記:從GC Roots中對堆對象進行可達性分析,找出存活對象
- 最終標記:修改並發期間變動的標記記錄
- 篩選回收:根據用戶指定的停頓時間制定回收計划
- 復制/清除過程后。回收區域的活性對象已經被集中回收到深藍色和深綠色區域。唯一和串行垃圾回收器不同的是,並行垃圾回收器是使用多線程來進行垃圾回收工作的。
G1的新生代收集跟ParNew類似,當新生代占用達到一定比例的時候,開始出發收集。和CMS類似,G1收集器收集老年代對象會有短暫停頓。
G1收集器的核心思想是在CMS基礎上增加了在有限的時間內盡可能高的收集效率。
幾種垃圾收集器的組合
總結
垃圾收集的核心還是從“標記-清理”算法基礎上的各種優化版本,每一種算法的都是從時間和空間兩個角度出發來達到最高效率,請根據個人應用特性來選擇最適合的垃圾收集器,好啦本文就說這么多,希望大家多思考多練習,歡迎留言討論。
參考
https://blog.csdn.net/high2011/article/details/80177473
https://blog.csdn.net/u011130752/article/details/50886939
https://www.cnblogs.com/grey-wolf/p/9217497.html
https://www.sohu.com/a/217151448_812245