這篇文章將講解垃圾回收的概念以及對那些區(qū)域進行垃圾回收割笙,最后講解幾種常見的垃圾回收算法咸灿。
概述
什么叫垃圾收集器郑趁?
需要思考GC需要完成的3件事情:
- 哪些內(nèi)存需要回收齐饮?
- 什么時候需要回收纬凤?
- 如何回收福贞?
下面介紹一下Java內(nèi)存運行時區(qū)域的各個部分,為什么有些區(qū)域需要回收停士,有些區(qū)域不需要回收挖帘?以及怎么去回收?
- 1恋技、程序計數(shù)器拇舀、虛擬機棧、本地方法棧3個區(qū)域隨線程而生蜻底,隨線程而滅骄崩;棧中的棧幀隨方法的進入和退出而有條不絮地執(zhí)行著出棧和入棧操作。因此這幾個區(qū)域的內(nèi)存分配和回收都具備確定性薄辅,在這幾個區(qū)域就不需要過多考慮回收的問題要拂,因為方法結(jié)束或者線程結(jié)束時,內(nèi)存自然就跟隨著回收了站楚。
- 2宇弛、Java堆和方法區(qū)則不一樣,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣源请,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運行期間時才能知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的谁尸,垃圾收集器所關(guān)注的是這部分內(nèi)存舅踪。
對象已死嗎?
在堆里面存放著Java世界中幾乎所有的對象實例良蛮,垃圾收集器在堆進行回收前抽碌,第一件事情就是確定這些對象之中哪些還“存活”著,哪些已經(jīng)“死去”(即不能再被任何途徑使用的對象)
引用計數(shù)算法
很多教科書判斷對象是否存活的算法是這樣的:給對象中添加一個引用計數(shù)器决瞳,每當(dāng)有一個地方引用它時货徙,計數(shù)器值加1;當(dāng)引用失效時皮胡,計數(shù)器就減1痴颊;任何時刻計數(shù)器為0的對象就是不可能在被使用的。
可達性分析算法
通過可達性分析算法(Reachability Analysis)來判定對象是否存活的屡贺。
基本思路:通過一系列的稱為GC Roots 的對象作為起始點蠢棱,從這些起始點往下搜索,搜索走過的路徑稱為引用鏈甩栈,當(dāng)一個對象和GC Roots沒有任何引用鏈(即GC Roots到這個對象時不可達的)泻仙,說明對象時無用的。
[圖片上傳失敗...(image-bcae71-1521271966954)]
在Java中可作為GC Roots的對象有下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象量没。
- 方法區(qū)中類靜態(tài)屬性引用的對象玉转。
- 方法區(qū)中常量引用的對象。
- 本地方法棧中引用的對象殴蹄。
再談引用
無論是通過引用計數(shù)算法判斷對象的引用數(shù)量究抓,還是通過可達性分析算法判斷對象的引用鏈?zhǔn)欠窨蛇_,判定對象是否存活都與引用有關(guān)饶套。
引用的四種類型:
-
強引用:就是指在程序代碼之中普遍存在的漩蟆,類似
Object obj = new Object()
這類引用,只要強引用還存在妓蛮,垃圾收集器永遠不會回收被引用的對象怠李。 - 軟引用:是用來描述一個還有用但并非必須的對象。對于軟引用關(guān)聯(lián)著的對象蛤克,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前捺癞,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內(nèi)存构挤,才會拋出內(nèi)存溢出異常髓介。
- 弱引用:也是用來描述非必須對象的,但是它的強度比軟引用更弱一些筋现,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾發(fā)生之前唐础。當(dāng)垃圾收集器工作時箱歧,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象
- 虛引用: 一個對象是否有虛引用的存在一膨,完全不會對其生存時間構(gòu)成影響呀邢,也無法通過虛引用來取得這個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時受到一個系統(tǒng)通知豹绪。
生存還是死亡
可達性分析法中不可達的對象也不是非死不可的价淌,而是處于緩刑階段。要宣告一個對象的死亡至少要經(jīng)過兩次標(biāo)記過程:
- 1瞒津、當(dāng)經(jīng)過可達性分析后發(fā)現(xiàn)對象與GC Roots不可達蝉衣,那么它會被第一次標(biāo)記并且進行一次刷選,刷選的條件是此對象是否有必要執(zhí)行finalize方法巷蚪。
- 2病毡、當(dāng)對象沒有覆蓋finalize方法或?qū)ο蟮膄inalize方法已經(jīng)被虛擬機執(zhí)行過。這兩種情況都會被視為不需要執(zhí)行finalizef方法钓辆。
如果這個對象有必要執(zhí)行finalize方法剪验,那么對象會被放在F-Queue的隊列中,并且會被由Java虛擬機自動創(chuàng)建的前联、低優(yōu)先級的Finalizer線程去執(zhí)行功戚。finalize方法是對象最后一次逃脫死亡的機會,在finalize方法后似嗤,GC將會對對象進行第二次標(biāo)記啸臀。如果對象在finalize方法中成功拯救自己,那么在第二次標(biāo)記時會被移出回收集合烁落,否則就真的被回收了乘粒。
回收方法區(qū)
很多人認(rèn)為方法區(qū)(虛擬機中的永久代)是沒有垃圾回收的,Java虛擬機規(guī)范也確實說過不要求虛擬機在方法區(qū)實現(xiàn)圾回收伤塌,因為方法區(qū)的垃圾收集效率很低灯萍。
方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:廢棄常量
和無用的類
。
- 回收廢棄常量:回收廢棄常量與回收J(rèn)ava堆中的對象類似每聪,以常量池中的字面量的回收為例:如果“abc”字符串存儲在常量池中旦棉,其他地方?jīng)]有任何對象引用常量池中的“abc”常量,那么進行垃圾回收時“abc”常量會被清理出常量池药薯。常量池中的其他類(接口)绑洛、方法、字段的符號引用也與此類似童本。
- 無用的類 判斷無用的類比廢棄常量條件苛刻得多真屯。必須滿足以下三個條件:
- 該類的所有實例都已被回收。
- 加載該類的ClassLoader已被回收
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用穷娱,無法在任何地方通過反射訪問該類绑蔫。
垃圾收集算法
標(biāo)記-清除算法
工作原理:算法主要分為兩個階段標(biāo)記和清除运沦,首先標(biāo)記出所有需要回收的對象,標(biāo)記完成后統(tǒng)一進行清除晾匠。
缺點:
- 1茶袒、效率問題:標(biāo)記和清除兩個過程效率都不高
- 2、空間問題:對象清除后會產(chǎn)生大量不連續(xù)的空間碎片凉馆,當(dāng)需要分配給大對象較大的內(nèi)存空間時會因為找不到足夠的連續(xù)空間而不得不提前出發(fā)下一次垃圾收集。
復(fù)制算法
為解決效率問題亡资,復(fù)制算法出現(xiàn)了:它將內(nèi)存空間分為大小相等的兩塊區(qū)域澜共,每次只使用其中一塊,當(dāng)進行垃圾收集時锥腻,將這塊區(qū)域中還存活的對象復(fù)制到另一塊嗦董,然后將這一塊內(nèi)存回收。這樣就不會產(chǎn)生內(nèi)存碎片的問題瘦黑。
缺點:這種算法實現(xiàn)簡單京革,運行高效,只是代價是每次只能使用內(nèi)存的一半幸斥,代價過高匹摇。
現(xiàn)在的商用虛擬機都采用這種收集算法回收新生代內(nèi)存。根IBM公司的研究表明甲葬,新生代中的內(nèi)存對象98%是朝生夕死的廊勃,所以不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden區(qū)域经窖,兩塊較小的Survivor區(qū)域坡垫。每次只使用一塊Eden區(qū)域和一塊Survivor區(qū)域,當(dāng)進行垃圾收集時画侣,將Eden區(qū)域和Survivor區(qū)域仍然存活的對象復(fù)制到另一塊Survivor區(qū)域冰悠,然后將Eden區(qū)域和使用過的Survivor區(qū)域清除。HotSpot虛擬機默認(rèn)的Eden和Survivor區(qū)域大小比例為8:1配乱,這樣只會浪費10%的內(nèi)存溉卓。
標(biāo)記-整理算法
復(fù)制算法在對象成活率較低的新生代比較適用,而對于對象成活率較高的老年代就需要進行較多的復(fù)制操作宪卿,效率明顯會減低的诵。所以針對老年代的特點,提出了標(biāo)記-整理算法:標(biāo)記清除過程仍然與標(biāo)記清楚算法一樣佑钾,只是在清除后將存活的對象都向一端移動西疤。
分代收集算法
當(dāng)前商業(yè)的虛擬機的垃圾收集算法都采用分代收集算法:根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊,一般把Java分為新生代和老年代休溶,在根據(jù)各個年代的特點選擇合適的收集算法代赁。在新生代中扰她,對象存活率低,適合使用復(fù)制算法芭碍,而老年代對象存活率較高徒役,適合使用標(biāo)記清除算法或標(biāo)記-整理算法。
參考資料
https://juejin.im/post/5aa0e8176fb9a028d663be1e
http://www.ityouknow.com/jvm/2017/08/29/GC-garbage-collection.html