對于垃圾回收機制我先拋出三個問題:
①哪些內存需要回收?
②什么時候回收?
③如何回收?
下面我們主要針對這三個問題來研究JVM GC
一、哪些內存需要回收?
1.JAVA使用可達性分析法來判斷對象是否需要回收。
這個算法的基本思路是通過一系列稱為“GC ROOTS”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC ROOTS沒有任何引用鏈的話,則此對象不可用。可回收。
GC ROOTS對象包括以下幾種:
①虛擬機棧(棧幀中的本地變量表)中引用的對象
②方法區中類靜態屬性引用的對象
③方法區中常量引用的對象
④本地方法棧JNI引用的對象
2.對象在作可達性分析后如果沒有與任何GC ROOTS關聯那么將會被標記篩選,如果它覆蓋了finalize()方法則它將會被放置在一個F-Queue隊列中,并由一個Finalizer線程去執行,如果在finalize方法中成功拯救了自己(將this引用賦值給每個類變量或者成員變量),則可避免被回收。但是強烈不建議使用對象的finalize()方法。在這里我只是把我知道的記錄一下。
二、什么時候回收?
1.安全點
程序在執行時并不是在所有地方都能停下來進行GC,只有到達安全點才能暫停。對于Safepoint,需要考慮的問題是如何在GC發生時讓所有線程都跑到最近的安全點再停頓下來。這里有兩種方案可選:
①搶占式中斷
搶占式中斷不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現線程中斷的地方不在安全點上,就恢復線程,讓它跑到安全點上。
②主動式中斷
主動式中斷不直接對線程操作,僅僅設置一個標志,各個線程執行時主動去輪詢這個標志,發現中斷標志為真時就自己中斷掛起,輪詢標志和安全點是重合的,另外加上創建對象需要分配內存的地方。
2.安全區域
安全區域是指在一段代碼中,引用關系不會發生變化,在這個區域的任何地方開始GC都是安全的。
線程執行到safe region中的代碼時,首先標識自己進入safe region,那在這段時間里JVM要發起GC時就不用管那些已經標識自己為safe region狀態的線程了。當線程要離開safe region時,檢查系統是否已經完成了根節點枚舉,如果完成了,那線程就繼續執行否則就等待直到可以離開safe region的信號為止。
三、如何回收?
1.回收算法:
1)標記-清除算法
缺點:①效率問題,標記和清除兩個過程的效率都不高;②空間問題,清除后會產生大量不連續的內存碎片,導致大對象無法分配而提前出發GC
2)復制算法
復制算法是將內存分為大小相等的兩塊,每次只是使用其中的一塊。當一塊內存使用完之后,將還存活的對象復制到另一塊內存中,然后清空內存。
缺點:內存容量只使用了一半。
現在的商業虛擬機都采用這種方式來回收新生代。使用一塊eden和兩塊survivor區,eden:survivor=8:1,每次只使用eden和一塊survivor,然后將存活對象復制到另一塊survivor上。
注意,這里我們不能保證每次回收都只有不多于10%的對象存活,當survivor空間不夠時需要依賴其他內存(老年代)進行分配擔保。
3)標記-整理算法
標記過程與標記-清除算法相同,后續不是直接對可回收對象進行清理,而是讓所有存活對象都向一端移動,然后直接清理掉端邊界以外的內存
4)分代收集算法
針對不同的內存區域采用不同的回收算法,比如年輕代采用復制算法,老年代采用標記-清除或者標記-整理算法。
四、垃圾回收器
1.serial收集器
單線程收集器,年輕代采用復制收集算法,老年代采用標記-整理算法
2.ParNew收集器
是Serial收集器的多線程版本
3.Parallel Scavenge收集器
采用復制算法的多線程新生代收集器,與ParNew不同的是,其目的是達到一個可控制的吞吐量。(吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間))
4.Parallel Old收集器
Parallel Scavenge的老年代版本。
5.CMS收集器
以獲取最短回收時間為目標的收集器,基于標記-清除算法
6.G1收集器
特點:
①并行與并發
②分代收集
G1不需要與其他收集器配合,管理整個GC堆
③空間整合
從整體上來看屬于標記-整理算法,從局部看屬于復制算法,不會產生內存碎片
④可預測停頓
?