深入理解java虛擬機(jī) 摘要
目錄:
一、自動(dòng)內(nèi)存管理機(jī)制
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)域
- HotSpot虛擬機(jī)對(duì)象探秘
- 實(shí)戰(zhàn):OutOfMemoryError異常
- 垃圾收集器與內(nèi)存分配策略
一阶女、自動(dòng)內(nèi)存管理機(jī)制
4.垃圾收集器與內(nèi)存分配策略
主要考慮 Java堆和方法區(qū) 內(nèi)存
-
對(duì)象死亡判斷
在堆里面存放著Java世界中幾乎所有的對(duì)象實(shí)例甩骏,垃圾收集器在對(duì)堆進(jìn)行回收前香罐,第一件事情就是要確定這些對(duì)象之中哪些還“存活”著牛欢,哪些已經(jīng)“死去”(即不可能再被任何途徑使用的對(duì)象)逸绎。
- 引用計(jì)數(shù)算法
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器惹恃,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1棺牧;當(dāng)引用失效時(shí)巫糙,計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的颊乘。
主流的Java虛擬機(jī)里面沒(méi)有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存参淹,其中最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題醉锄。例子:
public class Tests { public Object instance=null; private static final int MB=1024*1024; /** *這個(gè)成員屬性的唯一意義就是占點(diǎn)內(nèi)存,以便能在GC日志中看清楚是否被回收過(guò) */ public static void main(String[] args){ Tests objA=new Tests(); Tests objB=new Tests(); objA.instance=objB; objB.instance=objA; objA=null; objB=null; //假設(shè)在這行發(fā)生GC,objA和objB是否能被回收浙值? System.gc(); } }
啟動(dòng)參數(shù)
-XX:+PrintGCDetails
運(yùn)行結(jié)果(部分)
[GC (System.gc()) [PSYoungGen: 4601K->1144K(37888K)] 4601K->1152K(123904K), 0.0086625 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
從運(yùn)行結(jié)果中可以清楚看到恳不,GC日志中包含“4601K->1144K”,意味著虛擬機(jī)并沒(méi)有因?yàn)檫@兩個(gè)對(duì)象互相引用就不回收它們亥鸠,這也從側(cè)面說(shuō)明虛擬機(jī)并不是通過(guò)引用計(jì)數(shù)算法來(lái)判斷對(duì)象是否存活的妆够。- 可達(dá)性分析算法
在主流的商用程序語(yǔ)言(Java、C#负蚊,甚至包括前面提到的古老的Lisp)的主流實(shí)現(xiàn)中神妹,都是稱通過(guò)可達(dá)性分析(Reachability Analysis)來(lái)判定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn)家妆,從這些節(jié)點(diǎn)開(kāi)始向下搜索鸵荠,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)伤极,則證明此對(duì)象是不可用的蛹找。
在Java語(yǔ)言中,可作為GC Roots的對(duì)象包括下面幾種:
虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象哨坪。
方法區(qū)中類靜態(tài)屬性引用的對(duì)象庸疾。
方法區(qū)中常量引用的對(duì)象。
本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象当编。在JDK 1.2之后届慈,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(StrongReference)忿偷、軟引用(Soft Reference)金顿、弱引用(Weak Reference)、虛引用(PhantomReference)4種鲤桥,這4種引用強(qiáng)度依次逐漸減弱揍拆。
-
垃圾收集算法
- 標(biāo)記-清除算法
最基礎(chǔ)的收集算法是“標(biāo)記-清除”(Mark-Sweep)算法,如同它的名字一樣茶凳,算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象嫂拴,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象,它的標(biāo)記過(guò)程其實(shí)在前一節(jié)講述對(duì)象標(biāo)記判定時(shí)已經(jīng)介紹過(guò)了贮喧。之所以說(shuō)它是最基礎(chǔ)的收集算法顷牌,是因?yàn)楹罄m(xù)的收集算法都是基于這種思路并對(duì)其不足進(jìn)行改進(jìn)而得到的。
它的主要不足有兩個(gè):一個(gè)是效率問(wèn)題塞淹,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高窟蓝;另一個(gè)是空間問(wèn)題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致以后在程序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí)运挫,無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作状共。- 復(fù)制算法
為了解決效率問(wèn)題,一種稱為“復(fù)制”(Copying)的收集算法出現(xiàn)了谁帕,它將可用內(nèi)存按容量劃分為大小相等的兩塊峡继,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了匈挖,就將還存活著的對(duì)象復(fù)制到另外一塊上面碾牌,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。這樣使得每次都是對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收儡循,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況舶吗,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可择膝,實(shí)現(xiàn)簡(jiǎn)單誓琼,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為了原來(lái)的一半肴捉,未免太高了一點(diǎn)腹侣。
- 標(biāo)記-整理算法
復(fù)制收集算法在對(duì)象存活率較高時(shí)就要進(jìn)行較多的復(fù)制操作,效率將會(huì)變低齿穗。更關(guān)鍵的是傲隶,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保窃页,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況伦籍,所以在老年代一般不能直接選用這種算法。
根據(jù)老年代的特點(diǎn)腮出,有人提出了另外一種“標(biāo)記-整理”(Mark-Compact)算法,標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣芝薇,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理胚嘲,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存洛二,- 分代收集算法
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection)算法馋劈,這種算法并沒(méi)有什么新的思想,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊晾嘶。一般是把Java堆分為新生代和老年代妓雾,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āT谛律欣萦兀看卫占瘯r(shí)都發(fā)現(xiàn)有大批對(duì)象死去械姻,只有少量存活,那就選用復(fù)制算法机断,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集楷拳。而老年代中因?yàn)閷?duì)象存活率高绣夺、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記—清理”或者“標(biāo)記—整理”算法來(lái)進(jìn)行回收欢揖。