? ? ? ? 前面的章節(jié)了解到虛擬機(jī)的對(duì)象存活判定和垃圾回收基礎(chǔ)理論恨诱,但是具體如何實(shí)現(xiàn)才能保證虛擬機(jī)的高效運(yùn)行就不得不依托于嚴(yán)格的算法媳瞪。
一、 根節(jié)點(diǎn)枚舉
? ? ? ? 可達(dá)性分析算法從GC ROOTS找出引用鏈為例照宝,可作為GC ROOTS的節(jié)點(diǎn)主要在全局性的引用(常量蛇受、類靜態(tài)屬性)與執(zhí)行上下文(棧幀中的本地變量表)中,盡管目標(biāo)明確厕鹃,但是在方法區(qū)過(guò)大時(shí)兢仰,高效查找并不是一件簡(jiǎn)單的事。
? ? ? ? 目前剂碴,所有收集器在根節(jié)點(diǎn)枚舉時(shí)把将,都必須暫停用戶線程。
? ? ????根節(jié)點(diǎn)枚舉最長(zhǎng)的引用鏈可以做到與用戶線程一起并發(fā)忆矛,要確保枚舉期間對(duì)象的引用關(guān)系不會(huì)不斷的發(fā)生變化察蹲,枚舉根節(jié)點(diǎn)時(shí)暫停用戶線程是必要的。即便是CMS催训、G1等收集器也不例外递览。
????????當(dāng)用戶線程暫停后,其實(shí)并不需要一個(gè)不漏的檢查所有的執(zhí)行上下文和全局的引用位置瞳腌,虛擬機(jī)應(yīng)該是有辦法直接得到哪些地方存放著對(duì)象引用的,HotSpot使用被稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)做優(yōu)化镜雨,當(dāng)類加載完成時(shí)嫂侍,虛擬機(jī)就會(huì)把對(duì)象內(nèi)偏移量對(duì)應(yīng)的數(shù)據(jù)類型計(jì)算出來(lái),在編譯期也會(huì)在特定位置記錄棧和寄存器里引用的位置荚坞,因此挑宠,垃圾收集器在掃描時(shí)可以直接得到這些信息,而不需要一個(gè)不漏的從方法區(qū)的根節(jié)點(diǎn)開(kāi)始尋找颓影,如下:
二各淀、安全點(diǎn)
? ? ? ? OopMap結(jié)構(gòu)的引入,高效的完成GC ROOTS枚舉的同時(shí)诡挂,伴隨著更多的存儲(chǔ)空間碎浇,能導(dǎo)致OopMap內(nèi)容變化的指令很多,如果每一條指令都對(duì)應(yīng)一個(gè)OopMap璃俗,那么不僅存儲(chǔ)空間加劇奴璃,垃圾回收的成本也會(huì)提高。
? ? ? ? 針對(duì)上述問(wèn)題城豁,HotSpot認(rèn)為沒(méi)有必要為每條指令生成個(gè)一個(gè)OopMap苟穆,只用在特定的位置記錄,這些特定的位置被稱為安全點(diǎn),安全點(diǎn)決定程序執(zhí)行并非在任何指令的任何位置都能停頓下來(lái)開(kāi)始垃圾收集雳旅,而是必須到達(dá)安全點(diǎn)后才能夠暫停腺晾。對(duì)于安全點(diǎn)而言膨处,如何在垃圾收集發(fā)生時(shí),讓所有線程到達(dá)最近的安全點(diǎn)是不得不考慮的問(wèn)題。對(duì)此蔼紧,有兩種方案:搶先式中斷(Preemptive Suspension)?和主動(dòng)式中斷(Voluntary Suspension):
? ? ? ? (1)搶先式中斷:不需要線程執(zhí)行代碼去主動(dòng)配合,在垃圾收集時(shí)境蜕,系統(tǒng)會(huì)把所有用戶線程全部中斷撑教,如果發(fā)現(xiàn)有不在安全點(diǎn)上的用戶線程,就允許該線程繼續(xù)執(zhí)行偷遗,直到跑到安全點(diǎn)上再進(jìn)行中斷墩瞳。
? ? ? ? (2)主動(dòng)式中斷:設(shè)置一個(gè)標(biāo)志位,各線程執(zhí)行過(guò)程中不停的輪詢?cè)摌?biāo)志氏豌,一旦發(fā)現(xiàn)中斷標(biāo)志位true時(shí)喉酌,就自己在最近的安全點(diǎn)上主動(dòng)掛起,輪詢標(biāo)志的地方和安全點(diǎn)是重合的泵喘。
三泪电、安全區(qū)
? ? ? ? 針對(duì)于安全點(diǎn)的設(shè)計(jì),可能會(huì)引發(fā)線程無(wú)法響應(yīng)虛擬機(jī)中斷請(qǐng)求的場(chǎng)景(比如Sleep或者Blocked)纪铺,這時(shí)的線程無(wú)法走到安全點(diǎn)去掛起自己相速。因此,引入了另一概念鲜锚,安全區(qū)(Safe Region)突诬。
? ? ? ? 安全區(qū)是指能夠保證某一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化芜繁,該區(qū)域中任意地方開(kāi)始垃圾收集都是安全的旺隙。因此,安全區(qū)即視為是安全點(diǎn)的拉伸骏令。
? ? ? ? 用戶線程執(zhí)行到安全區(qū)的代碼時(shí)蔬捷,首先會(huì)標(biāo)識(shí)自己已經(jīng)進(jìn)入了安全區(qū),因此這段時(shí)間虛擬機(jī)發(fā)起垃圾回收時(shí)就不會(huì)去管這些處在安全區(qū)的線程榔袋。當(dāng)線程離開(kāi)安全區(qū)時(shí)周拐,它會(huì)檢查虛擬機(jī)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉,如果完成了凰兑,則繼續(xù)執(zhí)行速妖;否則,該線程會(huì)一直等待聪黎,直到收到離開(kāi)安全區(qū)的信號(hào)罕容。
四备恤、記憶集
? ? ? ? 在分代收集理論中,為了區(qū)分跨代對(duì)象锦秒,垃圾收集器在新生代建立了記憶集(Remebered Set)這種數(shù)據(jù)結(jié)構(gòu)露泊,避免把老年代也加入根節(jié)點(diǎn)枚舉的掃描范圍。記憶集是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)旅择。
? ? ? ? HotSpot采用一種被稱為“卡表”(Card Table)的方式去實(shí)現(xiàn)記憶集惭笑,卡表當(dāng)中存放了字節(jié)數(shù)組CARD_TABLE,數(shù)組的每一個(gè)元素對(duì)應(yīng)一塊特定大小的內(nèi)存區(qū)域生真,這個(gè)內(nèi)存區(qū)域被稱為“卡頁(yè)”沉噩。
? ? ? ? 一個(gè)卡頁(yè)的內(nèi)存中通常包含不止一個(gè)對(duì)象,只要卡頁(yè)內(nèi)存放的對(duì)象字段存在跨代指針柱蟀,就將該卡表的數(shù)組元素標(biāo)識(shí)為1川蒙,稱該元素?cái)?shù)據(jù)變臟(Dirty),反之长已,則標(biāo)識(shí)為0畜眨。在垃圾收集時(shí),只要篩選出卡表里變臟的元素术瓮,就能篩除存在跨代指針的元素康聂,把它們加入GC ROOTS一并掃描。
? ? ? ? 理論上講胞四,對(duì)象變臟的時(shí)間點(diǎn)是發(fā)生在引用類型字段賦值的那一刻恬汁,即其他分代區(qū)域中對(duì)象引用了本區(qū)域的對(duì)象。HotSpot虛擬機(jī)引入寫屏障(Write Barrier)技術(shù)來(lái)維護(hù)卡表狀態(tài)辜伟,類似于AOP切面的操作氓侧,在引用類型字段賦值時(shí)會(huì)產(chǎn)生一個(gè)環(huán)繞通知,供程序執(zhí)行額外的動(dòng)作游昼,在賦值前的寫屏障被稱為寫前屏障(Pre-Write Barrier),同理尝蠕,在賦值后的則稱為寫后屏障(Post-Write Barrier)烘豌。虛擬機(jī)會(huì)為所有的賦值操作生成相應(yīng)的指令。
? ? ? ? 為避免高并發(fā)場(chǎng)景下“偽共享”問(wèn)題看彼,即重復(fù)更新相同區(qū)域卡表的狀態(tài)而造成資源浪費(fèi)與性能損耗廊佩,JDK7之后,引入了參數(shù) -XX: +UseCondCardMark 來(lái)決定是否開(kāi)啟卡表更新的條件判斷靖榕,開(kāi)啟狀態(tài)下标锄,會(huì)先檢查卡表狀態(tài),如果卡表元素未被標(biāo)記過(guò)時(shí)茁计,才會(huì)將其標(biāo)記為變臟料皇。