一该贾、概述
GC
需要考慮的三個問題:
- 哪些內(nèi)存需要回收
- 什么時候回收
- 如何回收
在分析內(nèi)存區(qū)域的時候诉稍,我們把Java
運行時數(shù)據(jù)區(qū)分為兩個部分:
- 程序計數(shù)器赶熟、虛擬機棧茫负、本地方法棧:每個棧幀中分配多少內(nèi)存在類結(jié)構(gòu)確定下來就已知破停,因此這些區(qū)域的內(nèi)存分配和回收具備確定性翅楼,方法結(jié)束或線程結(jié)束時,內(nèi)存就跟著被回收了真慢。
-
Java
堆毅臊、方法區(qū):由于一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也不一樣黑界,只有在程序處于運行期間才能知道會創(chuàng)建哪些對象管嬉,因此這些區(qū)域的內(nèi)存分配和回收是動態(tài)的。
二朗鸠、如何判斷哪些是“存活”的實例
2.1 引用的分類
引用的定義:如果reference
類型的數(shù)據(jù)中存儲的數(shù)值代表的另外一塊內(nèi)存的起始地址蚯撩,就稱這塊內(nèi)存代表引用。
引用的分類:
- 強引用(
Object a = new Object()
):只要強引用存在烛占,垃圾回收器永遠不會回收掉被引用的對象胎挎。 - 軟引用(
SoftReference
):有用但并非必須,在系統(tǒng)將要發(fā)生OOM
異常之前忆家,將會把這些對象列進回收范圍中進行第二次回收犹菇。 - 弱引用(
WeakReference
):非必須對象,被弱引用的對象只能生存到下一次垃圾收集發(fā)生前弦赖。 - 虛引用(
PhantomReference
):不會對生存時間產(chǎn)生影響项栏,也無法通過虛引用來取得一個對象實例,設(shè)置虛引用的唯一目的就是能在這個對象被垃圾回收器回收時收到一個系統(tǒng)通知蹬竖。
2.2 引用計數(shù)法
給對象添加一個引用計數(shù)器沼沈,當(dāng)有一個地方引用它時就加一流酬,引用失效時就減一,當(dāng)計數(shù)器的值為零時表示它不可用列另。
但是它無法解決相互循環(huán)引用問題芽腾。
2.3 可達性分析
通過一系列的稱為GC Roots
的對象作為起始點,從這些節(jié)點開始向下搜索页衙,所走過的路徑稱為引用鏈摊滔。當(dāng)一個對象到GC Roots
沒有任何引用鏈時,表示這個對象不可用店乐,GC Roots
的類型有:
- 虛擬機棧中的局部變量表中引用的對象艰躺。
- 方法區(qū)中類靜態(tài)屬性引用的對象。
- 方法區(qū)中常量引用的對象眨八。
- 本地方法棧中
JNI
引用的對象腺兴。
2.4 finalize
方法對于內(nèi)存回收的影響
當(dāng)某個對象在經(jīng)過可達性分析后,發(fā)現(xiàn)它到GC Roots
沒有任何引用鏈時廉侧,那么它會被第一次標(biāo)記页响,并進行第一次篩選,篩選的結(jié)果有兩種情況:
- 沒有覆蓋
finalize()
方法或者虛擬機已經(jīng)調(diào)用過它的finalize()
方法:直接回收段誊。 - 其它情況:把這個對象放置在一個
F-Queue
的隊列中闰蚕,并在稍后由一個由虛擬機自動建立的、低優(yōu)先級的Finalizer
線程去執(zhí)行這個對象的finalize()
方法连舍,如對象要在finalize
方法中拯救自己没陡,只要重新與引用鏈的某個變量關(guān)聯(lián)即可,那么在第二次標(biāo)記時它將被移出“即將回收”的集合烟瞧,否則它將被回收诗鸭。
這種方法代價高昂,不確定性大参滴,無法保證各個對象的調(diào)用順序强岸,因此可以忘記這個方法的存在。
三砾赔、方法區(qū)的回收
對于方法區(qū)(HotSpot
中的永久代)主要回收兩部分內(nèi)容:廢棄常量和無用的類蝌箍。
廢棄常量
以常量池中字面量的回收為例,如果一個字符串abc
被放入了常量池中暴心,但是沒有任何一個String
對象引用它妓盲,那么就會被清理出常量池,常量池中其它類(接口)专普、方法悯衬、字段的符號引用也類似。-
類檀夹,同時滿足三個條件:
- 該類的所有實例已經(jīng)被回收
- 加載該類的
ClassLoader
已經(jīng)被回收 - 該類對應(yīng)的
java.lang.Class
對象沒有在任何地方被引用筋粗,無法在任何地方通過反射訪問該類的方法策橘。
四、垃圾收集算法基礎(chǔ)
4.1 標(biāo)記 - 清除算法
- 概念
首先標(biāo)記出所有需要回收的對象娜亿,在標(biāo)記完成后統(tǒng)一進行回收丽已。 - 缺點:
- 標(biāo)記和清除兩個過程效率不高。
- 產(chǎn)生內(nèi)存碎片买决,導(dǎo)致需要分配較大對象時沛婴,無法找到足夠的連續(xù)內(nèi)存而需要觸發(fā)一次
GC
操作。
4.2 復(fù)制算法
概念
將可用內(nèi)存劃分為大小相等的兩塊督赤,每次只使用其中的一塊嘁灯,當(dāng)一塊內(nèi)存用完了。則觸發(fā)一次GC
操作躲舌,將活著的對象復(fù)制到另一塊上旁仿,然后再把已使用的內(nèi)存空間一次清理掉。缺點
將內(nèi)存縮小為了原來的一半孽糖。現(xiàn)在商業(yè)虛擬機采用這種算法的改良版來實現(xiàn)新生代的回收
它把內(nèi)存按8:1:1
分為Eden/survivor0/survivor1
三塊:
需要分配內(nèi)存時,首先嘗試在Eden
區(qū)分配毅贮,如果Eden
區(qū)無法分配办悟,那么嘗試把活著的對象放到survivor0
中去:如果
survivor0
可以放入,那么放入之后清除Eden
區(qū)滩褥。-
如果
survivor0
不可以放入病蛉,那么嘗試把Eden
和survivor0
的存活對象放到survivor1
中:- 如果
survivor1
可以放入,那么放入survivor1
之后清除Eden
和survivor0
瑰煎,之后再把survivor1
中的對象復(fù)制到survivor0
中铺然,保持survivor1
一直為空。 - 如果
survivor1
不可以放入酒甸,那么直接把它們放入到老年代中魄健,并清除Eden
和survivor0
,這個過程也稱為分配擔(dān)保插勤。
- 如果
適用情況
由于復(fù)制算法在對象成活率較高時沽瘦,需要較多的復(fù)制操作,效率會變低农尖,所以在老年代中不能采用該算法析恋。
4.3 標(biāo)記 - 整理算法
- 概念
和標(biāo)記 - 清除算法類似,但后續(xù)步驟不是直接對可回收對象進行清理盛卡,而是讓所有存活的對象都向一端移動助隧,然后直接清除掉端邊界以外的內(nèi)存。 - 優(yōu)點
解決了標(biāo)記- 清除算法導(dǎo)致的內(nèi)存碎片問題和在存活率較高時復(fù)制算法效率低的問題滑沧。
4.4 分代收集算法
當(dāng)前商業(yè)虛擬機采用的方式并村,根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊巍实,一般是新生代和老年代:
- 新生代:每次垃圾收集時只有少量存活,選用復(fù)制算法的改良版橘霎,也就是上面說到的
Eden/survivor0/survivor1
的分配方式蔫浆。 - 老年代:對象存活率較高,且沒有分配擔(dān)保姐叁,必須用標(biāo)記 - 清除或標(biāo)記 - 整理算法來實現(xiàn)瓦盛。
五、Minor GC
和Major GC/Full GC
-
Minor GC
:發(fā)生在新生代的垃圾回收動作外潜,非常頻繁原环,回收速度也較快,采用的垃圾收集器有Serial
处窥、ParNew
嘱吗、Parallel Scavenge
。 -
Major GC/Full GC
:發(fā)生在老年代的GC
滔驾,經(jīng)常伴隨至少一次的Minor GC
谒麦,Major GC
的速度一般會比Minor GC
慢十倍以上,采用的垃圾收集器有CMS
哆致、Serial Old
绕德、Parallel Old
。
六摊阀、對象分配的原則
對象優(yōu)先在
Eden
區(qū)分配
當(dāng)Eden
區(qū)沒有足夠空間耻蛇,觸發(fā)一次Minor GC
。大對象直接進入老年代
例如很長的字符串以及數(shù)組胞此,經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來“安置”它們臣咖。長期存活的對象將進入老年代
如果Eden
區(qū)出生并進過第一次Minor GC
后,仍然存活漱牵,并且被成功復(fù)制到survivor
區(qū)中夺蛇,那么對象年齡變?yōu)橐唬?dāng)對象在survivor
中每熬過一次Minor GC
布疙,年齡就增加一蚊惯,當(dāng)年齡增加到一定程度,就會晉升到老年代中灵临。動態(tài)對象年齡綁定
如果survivor
空間中相同年齡所有對象大小的總和大于survivor
空間的一半截型,年齡大于或等于該年齡的對象就可以進入老年代,無須到達要求的年齡儒溉。空間分配擔(dān)保
在發(fā)生Minor GC
前宦焦,檢查老年代最大可用連續(xù)空間是否大于新生代所有對象總空間:大于,那么操作是安全的,不對老年代進行
Full GC
波闹。-
小于酝豪,檢查
HandlePromotionFailure
設(shè)置值是否允許失敗:- 允許:檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代對象的平均大芯椤:
- 大于:不對老年代進行
Full GC
孵淘。在這之后,因為有可能出現(xiàn)某次存活對象激增的情況歹篓,這種屬于冒險行為瘫证,如果出現(xiàn)了擔(dān)保失敗(也就是Eden
和survivor0
的存活對象既無法放入survivor1
庄撮,也無法放入老年代的連續(xù)空間中)背捌,那么會在失敗之后對老年代進行Full GC
。 - 小于:先對老年代進行一次
Full GC
洞斯。
- 大于:不對老年代進行
- 不允許:先對老年代執(zhí)行一次
Full GC
毡庆。
- 允許:檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代對象的平均大芯椤: