在本篇中,我們將繼續(xù)探討虛擬機(jī)自動(dòng)內(nèi)存管理系統(tǒng)的最重要一塊職能:虛擬機(jī)如何對(duì)死亡的對(duì)象進(jìn)行內(nèi)存回收。
本篇里面,所有涉及到具體JVM實(shí)現(xiàn)的內(nèi)容,仍然默認(rèn)為基于HotSpot虛擬機(jī)的實(shí)現(xiàn),后文不再單獨(dú)說(shuō)明。
對(duì)象存活的判定
當(dāng)一個(gè)對(duì)象不會(huì)再被使用的時(shí)候,我們會(huì)說(shuō)這對(duì)象已經(jīng)死亡。對(duì)象何時(shí)死亡,寫(xiě)程序的人應(yīng)當(dāng)是最清楚的。如果計(jì)算機(jī)也要弄清楚這件事情,就需要使用一些方法來(lái)進(jìn)行對(duì)象存活判定,常見(jiàn)的方法有引用計(jì)數(shù)(Reference Counting)有可達(dá)性分析(Reachability Analysis)兩種。
引用計(jì)數(shù)算法的大致思想是給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。它的實(shí)現(xiàn)簡(jiǎn)單,判定效率也很高,在大部分情況下它都是一個(gè)不錯(cuò)的算法,也有一些比較著名的應(yīng)用案例,例如微軟COM(Component Object Model)技術(shù)、使用ActionScript 3的FlashPlayer、Python語(yǔ)言和在游戲腳本領(lǐng)域得到許多應(yīng)用的Squirrel中都使用了引用計(jì)數(shù)算法進(jìn)行內(nèi)存管理。但是,至少Java語(yǔ)言里面沒(méi)有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,其中最主要原因是它沒(méi)有一個(gè)優(yōu)雅的方案去對(duì)象之間相互循環(huán)引用的問(wèn)題:當(dāng)兩個(gè)對(duì)象互相引用,即使它們都無(wú)法被外界使用時(shí),它們的引用計(jì)數(shù)器也不會(huì)為0。
許多主流程序語(yǔ)言中(如Java、C#、Lisp),都是使用可達(dá)性分析來(lái)判定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過(guò)一系列的稱(chēng)為GC根節(jié)點(diǎn)(GC Roots)的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始進(jìn)行向下搜索,搜索所走過(guò)的路徑成為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話(huà)來(lái)說(shuō)就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。如圖1所示,對(duì)象object 5、object 6、object 7雖然互相有關(guān)聯(lián),它們的引用并不為0,但是它們到GC Roots是不可達(dá)的,因此它們將會(huì)被判定為是可回收的對(duì)象。
圖1 可達(dá)性分析算法判定對(duì)象是否可回收
枚舉根節(jié)點(diǎn)
在Java語(yǔ)言里面,可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類(lèi)靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中。如果要使用可達(dá)性分析來(lái)判斷內(nèi)存是否可回收的,那分析工作必須在一個(gè)能保障一致性的快照中進(jìn)行——這里“一致性”的意思是整個(gè)分析期間整個(gè)執(zhí)行系統(tǒng)看起來(lái)就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn)分析過(guò)程中,對(duì)象引用關(guān)系還在不斷變化的情況,這點(diǎn)不滿(mǎn)足的話(huà)分析結(jié)果準(zhǔn)確性就無(wú)法保證。這點(diǎn)也是導(dǎo)致GC進(jìn)行時(shí)必須“Stop The World”的其中一個(gè)重要原因,即使是號(hào)稱(chēng)(幾乎)不會(huì)發(fā)生停頓的CMS收集器中,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的。
由于目前的主流JVM使用的都是準(zhǔn)確式GC(這個(gè)概念在第一篇中介紹過(guò)),所以當(dāng)執(zhí)行系統(tǒng)停頓下來(lái)之后,并不需要一個(gè)不漏地檢查完所有執(zhí)行上下文和全局的引用位置,虛擬機(jī)應(yīng)當(dāng)是有辦法直接得到哪些地方存放著對(duì)象引用。在HotSpot的實(shí)現(xiàn)中,是使用一組成為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的,在類(lèi)加載完成的時(shí)候,HotSpot就把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái),在JIT編譯過(guò)程中,也會(huì)在特定的位置記錄下棧里和寄存器里哪些位置是引用。這樣GC在掃描時(shí)就就可以直接得知這些信息了。下面的代碼清單1是HotSpot Client VM生成的一段String.hashCode()方法的本地代碼,可以看到在0x026eb7a9處的call指令有OopMap記錄,它指明了EBX寄存器和棧中偏移量為16的內(nèi)存區(qū)域中各有一個(gè)普通對(duì)象指針(Ordinary Object Pointer)的引用,有效范圍為從call指令開(kāi)始直到0x026eb730(指令流的起始位置)+142(OopMap記錄的偏移量)=0x026eb7be,即hlt指令為止。
代碼清單1 String.hashCode()方法的編譯后的本地代碼
安全點(diǎn)
在OopMap的協(xié)助下,HotSpot可以快速準(zhǔn)確地地完成GC Roots枚舉,但一個(gè)很現(xiàn)實(shí)的問(wèn)題隨之而來(lái):可能導(dǎo)致引用關(guān)系變化,或者說(shuō)OopMap內(nèi)容變化的指令非常多,如果為每一條指令都生成對(duì)應(yīng)的OopMap,那將會(huì)需要大量的額外空間,這樣GC的空間成本將會(huì)變得很高。
實(shí)際上HotSpot也的確沒(méi)有為每條指令都生成OopMap,前面已經(jīng)提到,只是在“特定的位置”記錄了這些信息,這些位置被稱(chēng)為安全點(diǎn)(Safepoint),即程序執(zhí)行時(shí)并非在所有的地方都能停頓下來(lái)開(kāi)始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停。Safepoint的選定既不能太少以至于讓GC等待時(shí)間太長(zhǎng),也不能過(guò)于頻繁以至于過(guò)分增大運(yùn)行時(shí)的負(fù)荷。所以安全點(diǎn)的選定基本上是以程序“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的——因?yàn)槊織l指令執(zhí)行的時(shí)間都非常短暫,程序不太可能因?yàn)橹噶盍鏖L(zhǎng)度太長(zhǎng)這個(gè)原因而過(guò)長(zhǎng)時(shí)間運(yùn)行,“長(zhǎng)時(shí)間執(zhí)行”的最明顯特征就是指令序列復(fù)用,例如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等,所以具有這些功能的指令才會(huì)產(chǎn)生Safepoint。
對(duì)于Sefepoint,另外一個(gè)需要考慮的問(wèn)題是如何讓GC發(fā)生時(shí),讓所有線(xiàn)程(這里不包括執(zhí)行JNI調(diào)用的線(xiàn)程)都跑到最近的安全點(diǎn)上再停頓下來(lái)。我們有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動(dòng)式中斷(Voluntary Suspension),搶先式中斷不需要線(xiàn)程的執(zhí)行代碼主動(dòng)去配合,在GC發(fā)生時(shí),首先把所有線(xiàn)程全部中斷,如果發(fā)現(xiàn)有線(xiàn)程中斷的地方不在安全點(diǎn)上,就恢復(fù)線(xiàn)程,讓它跑到安全點(diǎn)上。現(xiàn)在幾乎沒(méi)有虛擬機(jī)實(shí)現(xiàn)采用搶先式中斷來(lái)暫停線(xiàn)程響應(yīng)GC事件。
而主動(dòng)式中斷的思想是當(dāng)GC需要中斷線(xiàn)程的時(shí)候,不直接對(duì)線(xiàn)程操作,僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線(xiàn)程執(zhí)行時(shí)主動(dòng)去輪詢(xún)這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起。輪詢(xún)標(biāo)志的地方和安全點(diǎn)是重合的,另外再加上創(chuàng)建對(duì)象需要分配內(nèi)存的地方。下面的代碼清單2中的test指令是HotSpot生成的輪詢(xún)指令,當(dāng)需要暫停線(xiàn)程時(shí),虛擬機(jī)把0x160100的內(nèi)存頁(yè)設(shè)置為不可讀,那線(xiàn)程執(zhí)行到test指令時(shí)就會(huì)停頓等待,這樣一條指令便完成線(xiàn)程中斷了。
代碼清單2 輪詢(xún)指令
安全區(qū)域
使用Safepoint似乎已經(jīng)完美解決如何進(jìn)入GC的問(wèn)題了,但實(shí)際情況卻并不一定。Safepoint機(jī)制保證了程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入GC的Safepoint。但是,程序“不執(zhí)行”的時(shí)候呢?所謂的程序不執(zhí)行就是沒(méi)有分配CPU時(shí)間,典型的例子就是線(xiàn)程處于Sleep狀態(tài)或者Blocked狀態(tài),這時(shí)候線(xiàn)程無(wú)法響應(yīng)JVM的中斷請(qǐng)求,走到安全的地方去中斷掛起,JVM也顯然不太可能等待線(xiàn)程重新被分配CPU時(shí)間。對(duì)于這種情況,就需要安全區(qū)域(Safe Region)來(lái)解決。
安全區(qū)域是指在一段代碼片段之中,引用關(guān)系不會(huì)發(fā)生變化。在這個(gè)區(qū)域中任意地方開(kāi)始GC都是安全的。我們也可以把Safe Region看作是被擴(kuò)展了的Safepoint。
在線(xiàn)程執(zhí)行到Safe Region里面的代碼時(shí),首先標(biāo)識(shí)自己已經(jīng)進(jìn)入了Safe Region,那樣當(dāng)這段時(shí)間里JVM要發(fā)起GC,就不用管標(biāo)識(shí)自己為Safe Region狀態(tài)的線(xiàn)程了。在線(xiàn)程要離開(kāi)Safe Region時(shí),它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者是整個(gè)GC過(guò)程),如果完成了,那線(xiàn)程就繼續(xù)執(zhí)行,否則它就必須等待直到收到可以安全離開(kāi)Safe Region的信號(hào)為止。
到這里,我們簡(jiǎn)單介紹了虛擬機(jī)如何去發(fā)起內(nèi)存回收的問(wèn)題,但是虛擬機(jī)如何具體地進(jìn)行內(nèi)存回收動(dòng)作仍然未涉及到。因?yàn)閮?nèi)存回收如何進(jìn)行是由虛擬機(jī)所采用的GC收集器所決定的,而通常虛擬機(jī)中往往不止有一種GC收集器,像目前(JDK 7時(shí)代)的HotSpot里面就包含有Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、Concurrent Mark Sweep和Garbage First七種收集器,在下一篇中,我們將以最新最先進(jìn)的Garbage First(G1)收集器為例,介紹內(nèi)存回收的具體過(guò)程。
轉(zhuǎn)載地址:http://www.infoq.com/cn/articles/jvm-memory-collection
本篇里面,所有涉及到具體JVM實(shí)現(xiàn)的內(nèi)容,仍然默認(rèn)為基于HotSpot虛擬機(jī)的實(shí)現(xiàn),后文不再單獨(dú)說(shuō)明。
對(duì)象存活的判定
當(dāng)一個(gè)對(duì)象不會(huì)再被使用的時(shí)候,我們會(huì)說(shuō)這對(duì)象已經(jīng)死亡。對(duì)象何時(shí)死亡,寫(xiě)程序的人應(yīng)當(dāng)是最清楚的。如果計(jì)算機(jī)也要弄清楚這件事情,就需要使用一些方法來(lái)進(jìn)行對(duì)象存活判定,常見(jiàn)的方法有引用計(jì)數(shù)(Reference Counting)有可達(dá)性分析(Reachability Analysis)兩種。
引用計(jì)數(shù)算法的大致思想是給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。它的實(shí)現(xiàn)簡(jiǎn)單,判定效率也很高,在大部分情況下它都是一個(gè)不錯(cuò)的算法,也有一些比較著名的應(yīng)用案例,例如微軟COM(Component Object Model)技術(shù)、使用ActionScript 3的FlashPlayer、Python語(yǔ)言和在游戲腳本領(lǐng)域得到許多應(yīng)用的Squirrel中都使用了引用計(jì)數(shù)算法進(jìn)行內(nèi)存管理。但是,至少Java語(yǔ)言里面沒(méi)有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,其中最主要原因是它沒(méi)有一個(gè)優(yōu)雅的方案去對(duì)象之間相互循環(huán)引用的問(wèn)題:當(dāng)兩個(gè)對(duì)象互相引用,即使它們都無(wú)法被外界使用時(shí),它們的引用計(jì)數(shù)器也不會(huì)為0。
許多主流程序語(yǔ)言中(如Java、C#、Lisp),都是使用可達(dá)性分析來(lái)判定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過(guò)一系列的稱(chēng)為GC根節(jié)點(diǎn)(GC Roots)的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始進(jìn)行向下搜索,搜索所走過(guò)的路徑成為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話(huà)來(lái)說(shuō)就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。如圖1所示,對(duì)象object 5、object 6、object 7雖然互相有關(guān)聯(lián),它們的引用并不為0,但是它們到GC Roots是不可達(dá)的,因此它們將會(huì)被判定為是可回收的對(duì)象。

圖1 可達(dá)性分析算法判定對(duì)象是否可回收
枚舉根節(jié)點(diǎn)
在Java語(yǔ)言里面,可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類(lèi)靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中。如果要使用可達(dá)性分析來(lái)判斷內(nèi)存是否可回收的,那分析工作必須在一個(gè)能保障一致性的快照中進(jìn)行——這里“一致性”的意思是整個(gè)分析期間整個(gè)執(zhí)行系統(tǒng)看起來(lái)就像被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn)分析過(guò)程中,對(duì)象引用關(guān)系還在不斷變化的情況,這點(diǎn)不滿(mǎn)足的話(huà)分析結(jié)果準(zhǔn)確性就無(wú)法保證。這點(diǎn)也是導(dǎo)致GC進(jìn)行時(shí)必須“Stop The World”的其中一個(gè)重要原因,即使是號(hào)稱(chēng)(幾乎)不會(huì)發(fā)生停頓的CMS收集器中,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的。
由于目前的主流JVM使用的都是準(zhǔn)確式GC(這個(gè)概念在第一篇中介紹過(guò)),所以當(dāng)執(zhí)行系統(tǒng)停頓下來(lái)之后,并不需要一個(gè)不漏地檢查完所有執(zhí)行上下文和全局的引用位置,虛擬機(jī)應(yīng)當(dāng)是有辦法直接得到哪些地方存放著對(duì)象引用。在HotSpot的實(shí)現(xiàn)中,是使用一組成為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的,在類(lèi)加載完成的時(shí)候,HotSpot就把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái),在JIT編譯過(guò)程中,也會(huì)在特定的位置記錄下棧里和寄存器里哪些位置是引用。這樣GC在掃描時(shí)就就可以直接得知這些信息了。下面的代碼清單1是HotSpot Client VM生成的一段String.hashCode()方法的本地代碼,可以看到在0x026eb7a9處的call指令有OopMap記錄,它指明了EBX寄存器和棧中偏移量為16的內(nèi)存區(qū)域中各有一個(gè)普通對(duì)象指針(Ordinary Object Pointer)的引用,有效范圍為從call指令開(kāi)始直到0x026eb730(指令流的起始位置)+142(OopMap記錄的偏移量)=0x026eb7be,即hlt指令為止。
代碼清單1 String.hashCode()方法的編譯后的本地代碼
[Verified Entry Point] 0x026eb730: mov %eax,-0x8000(%esp) ………… ;; ImplicitNullCheckStub slow case 0x026eb7a9: call 0x026e83e0 ; OopMap{ebx=Oop [16]=Oop off=142} ;*caload ; - java.lang.String::hashCode@48 (line 1489) ; {runtime_call} 0x026eb7ae: push $0x83c5c18 ; {external_word} 0x026eb7b3: call 0x026eb7b8 0x026eb7b8: pusha 0x026eb7b9: call 0x0822bec0 ; {runtime_call} 0x026eb7be: hlt
安全點(diǎn)
在OopMap的協(xié)助下,HotSpot可以快速準(zhǔn)確地地完成GC Roots枚舉,但一個(gè)很現(xiàn)實(shí)的問(wèn)題隨之而來(lái):可能導(dǎo)致引用關(guān)系變化,或者說(shuō)OopMap內(nèi)容變化的指令非常多,如果為每一條指令都生成對(duì)應(yīng)的OopMap,那將會(huì)需要大量的額外空間,這樣GC的空間成本將會(huì)變得很高。
實(shí)際上HotSpot也的確沒(méi)有為每條指令都生成OopMap,前面已經(jīng)提到,只是在“特定的位置”記錄了這些信息,這些位置被稱(chēng)為安全點(diǎn)(Safepoint),即程序執(zhí)行時(shí)并非在所有的地方都能停頓下來(lái)開(kāi)始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停。Safepoint的選定既不能太少以至于讓GC等待時(shí)間太長(zhǎng),也不能過(guò)于頻繁以至于過(guò)分增大運(yùn)行時(shí)的負(fù)荷。所以安全點(diǎn)的選定基本上是以程序“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的——因?yàn)槊織l指令執(zhí)行的時(shí)間都非常短暫,程序不太可能因?yàn)橹噶盍鏖L(zhǎng)度太長(zhǎng)這個(gè)原因而過(guò)長(zhǎng)時(shí)間運(yùn)行,“長(zhǎng)時(shí)間執(zhí)行”的最明顯特征就是指令序列復(fù)用,例如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等,所以具有這些功能的指令才會(huì)產(chǎn)生Safepoint。
對(duì)于Sefepoint,另外一個(gè)需要考慮的問(wèn)題是如何讓GC發(fā)生時(shí),讓所有線(xiàn)程(這里不包括執(zhí)行JNI調(diào)用的線(xiàn)程)都跑到最近的安全點(diǎn)上再停頓下來(lái)。我們有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動(dòng)式中斷(Voluntary Suspension),搶先式中斷不需要線(xiàn)程的執(zhí)行代碼主動(dòng)去配合,在GC發(fā)生時(shí),首先把所有線(xiàn)程全部中斷,如果發(fā)現(xiàn)有線(xiàn)程中斷的地方不在安全點(diǎn)上,就恢復(fù)線(xiàn)程,讓它跑到安全點(diǎn)上。現(xiàn)在幾乎沒(méi)有虛擬機(jī)實(shí)現(xiàn)采用搶先式中斷來(lái)暫停線(xiàn)程響應(yīng)GC事件。
而主動(dòng)式中斷的思想是當(dāng)GC需要中斷線(xiàn)程的時(shí)候,不直接對(duì)線(xiàn)程操作,僅僅簡(jiǎn)單地設(shè)置一個(gè)標(biāo)志,各個(gè)線(xiàn)程執(zhí)行時(shí)主動(dòng)去輪詢(xún)這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起。輪詢(xún)標(biāo)志的地方和安全點(diǎn)是重合的,另外再加上創(chuàng)建對(duì)象需要分配內(nèi)存的地方。下面的代碼清單2中的test指令是HotSpot生成的輪詢(xún)指令,當(dāng)需要暫停線(xiàn)程時(shí),虛擬機(jī)把0x160100的內(nèi)存頁(yè)設(shè)置為不可讀,那線(xiàn)程執(zhí)行到test指令時(shí)就會(huì)停頓等待,這樣一條指令便完成線(xiàn)程中斷了。
代碼清單2 輪詢(xún)指令
0x01b6d627: call 0x01b2b210 ; OopMap{[60]=Oop off=460} ;*invokeinterface size ; - Client1::main@113 (line 23) ; {virtual_call} 0x01b6d62c: nop ; OopMap{[60]=Oop off=461} ;*if_icmplt ; - Client1::main@118 (line 23) 0x01b6d62d: test %eax,0x160100 ; {poll} 0x01b6d633: mov 0x50(%esp),%esi 0x01b6d637: cmp %eax,%esi
安全區(qū)域
使用Safepoint似乎已經(jīng)完美解決如何進(jìn)入GC的問(wèn)題了,但實(shí)際情況卻并不一定。Safepoint機(jī)制保證了程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入GC的Safepoint。但是,程序“不執(zhí)行”的時(shí)候呢?所謂的程序不執(zhí)行就是沒(méi)有分配CPU時(shí)間,典型的例子就是線(xiàn)程處于Sleep狀態(tài)或者Blocked狀態(tài),這時(shí)候線(xiàn)程無(wú)法響應(yīng)JVM的中斷請(qǐng)求,走到安全的地方去中斷掛起,JVM也顯然不太可能等待線(xiàn)程重新被分配CPU時(shí)間。對(duì)于這種情況,就需要安全區(qū)域(Safe Region)來(lái)解決。
安全區(qū)域是指在一段代碼片段之中,引用關(guān)系不會(huì)發(fā)生變化。在這個(gè)區(qū)域中任意地方開(kāi)始GC都是安全的。我們也可以把Safe Region看作是被擴(kuò)展了的Safepoint。
在線(xiàn)程執(zhí)行到Safe Region里面的代碼時(shí),首先標(biāo)識(shí)自己已經(jīng)進(jìn)入了Safe Region,那樣當(dāng)這段時(shí)間里JVM要發(fā)起GC,就不用管標(biāo)識(shí)自己為Safe Region狀態(tài)的線(xiàn)程了。在線(xiàn)程要離開(kāi)Safe Region時(shí),它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或者是整個(gè)GC過(guò)程),如果完成了,那線(xiàn)程就繼續(xù)執(zhí)行,否則它就必須等待直到收到可以安全離開(kāi)Safe Region的信號(hào)為止。
到這里,我們簡(jiǎn)單介紹了虛擬機(jī)如何去發(fā)起內(nèi)存回收的問(wèn)題,但是虛擬機(jī)如何具體地進(jìn)行內(nèi)存回收動(dòng)作仍然未涉及到。因?yàn)閮?nèi)存回收如何進(jìn)行是由虛擬機(jī)所采用的GC收集器所決定的,而通常虛擬機(jī)中往往不止有一種GC收集器,像目前(JDK 7時(shí)代)的HotSpot里面就包含有Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、Concurrent Mark Sweep和Garbage First七種收集器,在下一篇中,我們將以最新最先進(jìn)的Garbage First(G1)收集器為例,介紹內(nèi)存回收的具體過(guò)程。
轉(zhuǎn)載地址:http://www.infoq.com/cn/articles/jvm-memory-collection
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
