? ? ? ? 在Java中有一套完整的內(nèi)存動(dòng)態(tài)分配和垃圾收集(Garbage Collection,GC)機(jī)制剩檀,可以實(shí)現(xiàn)自動(dòng)化的內(nèi)存動(dòng)態(tài)分配和垃圾回收逆屡,了解這一機(jī)制,我們可以在排查內(nèi)存溢出赘被、內(nèi)存泄漏問(wèn)題時(shí)悴侵,實(shí)現(xiàn)系統(tǒng)更高并發(fā)量時(shí)對(duì)這一自動(dòng)化技術(shù)實(shí)施監(jiān)控和調(diào)節(jié)瞧剖。
? ? ? ? 在Java內(nèi)存運(yùn)行時(shí),程序計(jì)數(shù)器畜挨、虛擬機(jī)棧筒繁、本地方法棧的內(nèi)存分配和回收都是已知確定的,因此不用過(guò)多考慮回收問(wèn)題巴元。而Java堆和方法區(qū)內(nèi)存的分配和回收是動(dòng)態(tài)的毡咏,垃圾收集器關(guān)注的是這部分的內(nèi)存。
判斷對(duì)象是否死亡
判斷對(duì)象是否死亡有兩種算法:
(1)引用計(jì)數(shù)算法:給對(duì)象添加一個(gè)計(jì)數(shù)器逮刨,當(dāng)有一個(gè)地方引用它時(shí)計(jì)數(shù)器值加1呕缭,當(dāng)引用失效時(shí),計(jì)數(shù)器值減1修己。計(jì)數(shù)器為0的對(duì)象是不再被引用的恢总,當(dāng)該算法無(wú)法解決的是對(duì)象之間相互循環(huán)引用的問(wèn)題,關(guān)于這個(gè)問(wèn)題睬愤,可參考:http://blog.csdn.net/u010253968/article/details/51160703
(2)可達(dá)性分析算法:當(dāng)一個(gè)對(duì)象沒(méi)有被稱(chēng)為“GC Roots”的對(duì)象直接間接引用時(shí)片仿,及GC Roots到該對(duì)象不可達(dá)時(shí),此對(duì)象是不可用的尤辱。例如:GC Roots對(duì)象引用了object1砂豌,object1引用了object2厢岂,那么object1和object2都是可用的,因?yàn)樗鼈兌急籊C Roots對(duì)象直接或間接引用了阳距。而如果object3引用了object4塔粒,object4引用了object5,盡管它們都有被其他對(duì)象引用筐摘,但它們都沒(méi)有被GC Roots對(duì)象引用卒茬,所以它們都是不可用的。而GC Roots對(duì)象包括以下幾種:
a.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
b.方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
c.方法區(qū)中常量引用的對(duì)象
d.本地方法棧中JNI(Native方法)引用的對(duì)象
引用
引用分為四種:
(1)強(qiáng)引用:程序代碼之中普遍存在的咖熟,只要強(qiáng)引用還在圃酵,垃圾收集器永遠(yuǎn)不會(huì)回收被引用的對(duì)象
(2)軟引用:有用但并非必要的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前馍管,將會(huì)把這些對(duì)象列入回收范圍進(jìn)行第二次回收
(3)弱引用:被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前
(4)虛引用:虛引用不會(huì)對(duì)對(duì)象的生存周期構(gòu)成影響辜昵,也無(wú)法通過(guò)虛引用來(lái)獲取一個(gè)對(duì)象,虛引用的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知
對(duì)象的自我拯救
在可達(dá)性分析算法中的不可達(dá)對(duì)象咽斧,還會(huì)經(jīng)過(guò)一次篩選,躬存,篩選出有必要執(zhí)行finalize()方法的對(duì)象张惹,然后虛擬機(jī)會(huì)觸發(fā)這個(gè)方法,但并不一定會(huì)等待到它運(yùn)行結(jié)束岭洲。如果對(duì)象要在finalize()方法中拯救自己宛逗,可以在這個(gè)方法中重新與GC Roots對(duì)象建立直接或間接的關(guān)聯(lián)就可以存活。但在實(shí)際開(kāi)發(fā)中因盡量避免該方法
回收方法區(qū)
在方法區(qū)中垃圾收集效率比較低盾剩,一次能回收的空間比較少雷激,主要回收兩部分內(nèi)容:廢棄常量和無(wú)用的類(lèi),類(lèi)需要滿(mǎn)足以下條件才能算是無(wú)用的類(lèi):
(1)該類(lèi)的所有實(shí)例都已經(jīng)被回收告私,也就是Java堆中不存在該類(lèi)的任何實(shí)例
(2)加載該類(lèi)的ClassLoader已經(jīng)被回收
(3)該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用屎暇,無(wú)法在任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)的方法
垃圾收集算法
標(biāo)記-清除算法:首先標(biāo)記出需要回收的對(duì)象,然后統(tǒng)一回收驻粟,是最基本的收集算法根悼,其它算法是對(duì)其不足進(jìn)行改進(jìn)而得到的。主要有兩個(gè)不足:一是效率不高蜀撑,二是會(huì)產(chǎn)生大量不連續(xù)的空間碎片
復(fù)制算法:將內(nèi)存分為兩塊挤巡,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了酷麦,就把存活的對(duì)象復(fù)制到另一塊矿卑,然后清空使用過(guò)的內(nèi)存。這種算法的代價(jià)是將內(nèi)存縮小為原來(lái)的一半
標(biāo)記-整理算法:標(biāo)記過(guò)程與“標(biāo)記-清理”算法一樣沃饶,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理母廷,而是所有存活對(duì)象都向一端移動(dòng)轻黑,然后直接清理邊界以外的內(nèi)存
分代收集算法:把Java堆分為新生代和老年代,對(duì)應(yīng)每次垃圾收集都有大量對(duì)象死去的新生代采用復(fù)制算法徘意,這樣復(fù)制的成本刑υ谩;而對(duì)于對(duì)象存活率高的老年代椎咧,使用“標(biāo)記-清除”算法或者“標(biāo)記-整理”進(jìn)行回收
HotSpot的算法實(shí)現(xiàn)
HotSpot虛擬機(jī)在實(shí)現(xiàn)對(duì)象存活判定算法和垃圾回收算法時(shí)玖详,必須保證虛擬機(jī)高效運(yùn)行。
枚舉根節(jié)點(diǎn):
枚舉根節(jié)點(diǎn)對(duì)時(shí)間的敏感性:一方面勤讽,在垃圾回收過(guò)程中蟋座,用可達(dá)性分析算法判斷對(duì)象是否存活時(shí),為保證判斷結(jié)果的準(zhǔn)確性脚牍,必須停頓所有Java執(zhí)行線(xiàn)程使對(duì)象引用關(guān)系不發(fā)生變化向臀,所以枚舉根節(jié)點(diǎn)必須停頓。另一方面诸狭,可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類(lèi)靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)券膀,在很多應(yīng)用里,僅僅在方法區(qū)中就有幾百兆驯遇,如果要逐個(gè)檢查它們直接或間接引用的對(duì)象芹彬,會(huì)消耗很多時(shí)間,而消耗的時(shí)間越多叉庐,停頓的時(shí)間越長(zhǎng)舒帮。
解決這些不足的方法:執(zhí)行系統(tǒng)停頓時(shí),不需要逐個(gè)檢查陡叠,而是在類(lèi)加載完成時(shí)玩郊,就把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái)并用特定數(shù)據(jù)結(jié)構(gòu)表示出來(lái),在JIT編譯過(guò)程中枉阵,也會(huì)在特定的位置使用特定的數(shù)據(jù)結(jié)構(gòu)記錄下棧和寄存器中那些位置是引用译红,如Hotpot使用一組稱(chēng)為OopMap的數(shù)據(jù)結(jié)構(gòu)。這樣岭妖,GC 在掃描是可以直接得知這些信息临庇。
安全點(diǎn):
如果每一條指令都生成對(duì)應(yīng)的OopMap,則需要大量的額外空間昵慌,GC的空間成本會(huì)變得非常高假夺,所以只是在“特定的位置”生成了對(duì)應(yīng)的OopMap,這些位置就成為安全點(diǎn)(Safepoint)斋攀,程序在執(zhí)行到安全點(diǎn)才能停頓下來(lái)GC已卷。一般指令序列復(fù)用如方法調(diào)用、循環(huán)跳轉(zhuǎn)淳蔼、異常跳轉(zhuǎn)等情況下侧蘸,才會(huì)產(chǎn)生安全點(diǎn)裁眯,因?yàn)檫@些情況需要長(zhǎng)時(shí)間執(zhí)行。
對(duì)應(yīng)于安全點(diǎn)讳癌,需要考慮如何在GC發(fā)生時(shí)讓所有的線(xiàn)程執(zhí)行到最近的安全點(diǎn)再停頓下來(lái)穿稳,有兩種方法:
(1)搶先式中斷:GC發(fā)生時(shí),先把所以線(xiàn)程中斷晌坤,如果有線(xiàn)程中斷的地方不在安全點(diǎn)逢艘,就恢復(fù)線(xiàn)程,讓它執(zhí)行到安全點(diǎn)上骤菠,現(xiàn)在幾乎已經(jīng)不使用這種方式它改。
(2)主動(dòng)式中斷:當(dāng)GC需要中斷線(xiàn)程時(shí),不對(duì)線(xiàn)程操作商乎,只設(shè)置一個(gè)標(biāo)志央拖,各個(gè)線(xiàn)程主動(dòng)去輪詢(xún)這個(gè)標(biāo)志,如果這個(gè)中斷標(biāo)志為真就自己中斷掛起鹉戚,一般輪詢(xún)標(biāo)志的地方與安全點(diǎn)重合鲜戒。
安全區(qū)域:
線(xiàn)程處于Sleep狀態(tài)或者Blocked狀態(tài)等“不執(zhí)行”的狀態(tài)時(shí),無(wú)法響應(yīng)JVM的中斷請(qǐng)求抹凳,執(zhí)行到安全點(diǎn)然后中斷掛起袍啡,這時(shí)候就需要安全區(qū)域(Safe Region)來(lái)解決。安全區(qū)域指在一段代碼片段中却桶,引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域中能中的任意地方開(kāi)始GC都是安全的蔗牡。
當(dāng)線(xiàn)程執(zhí)行到Safe Region中的代碼時(shí)颖系,首先標(biāo)識(shí)自己進(jìn)入了安全區(qū)域,當(dāng)JVM要發(fā)起GC時(shí)辩越,就不用管標(biāo)志自己為安全區(qū)狀態(tài)的線(xiàn)程了嘁扼。當(dāng)線(xiàn)程要離開(kāi)安全區(qū)域時(shí),要檢查系統(tǒng)是否完成了根節(jié)點(diǎn)枚舉或者整個(gè)GC過(guò)程黔攒,如果完成了趁啸,就可以離開(kāi)安全區(qū)域,否則就要等到收到可以離開(kāi)Safe Region的信號(hào)為止督惰。