垃圾收集相關(guān)知識
思維導(dǎo)圖
回收的對象
堆搪柑,方法區(qū)(方法區(qū)虛擬機不要求實現(xiàn))
如何判斷一個對象可以回收
引用計數(shù)算法
主流的Java虛擬機沒有使用該算法。因為簡單的引用計數(shù)無法解決循環(huán)引用問題雁乡,需要很多額外的操作
可達性分析算法
GC ROOT 到該對象是否可達
能夠作為GC ROOT的對象
- 在虛擬機棧(棧幀中的本地變量表)中引用的對象,譬如各個線程被調(diào)用的方法堆棧中使用到的 參數(shù)、局部變量哺眯、臨時變量等呈宇。
- 在方法區(qū)中類靜態(tài)屬性引用的對象好爬,譬如Java類的引用類型靜態(tài)變量。
- 在方法區(qū)中常量引用的對象甥啄,譬如字符串常量池(String Table)里的引用存炮。
- 在本地方法棧中JNI(即通常所說的Native方法)引用的對象。
- Java虛擬機內(nèi)部的引用蜈漓,如基本數(shù)據(jù)類型對應(yīng)的Class對象穆桂,一些常駐的異常對象(比如 NullPointExcepiton、OutOfMemoryError)等融虽,還有系統(tǒng)類加載器享完。
- 所有被同步鎖(synchronized關(guān)鍵字)持有的對象。
- 反映Java虛擬機內(nèi)部情況的JMXBean有额、JVMTI中注冊的回調(diào)般又、本地代碼緩存等。
- 同時還會有一些其他對象被“臨時性”的加入
關(guān)于GC Roots根結(jié)點枚舉的一個優(yōu)化
首先要先明確一個前提:虛擬機(就算是幾乎不會發(fā)生停頓的CMS巍佑、G1茴迁、ZGC等收集器)在進行根結(jié)點枚舉的時候,都是需要STW的萤衰。因為根結(jié)點枚舉始終要在一個能夠保障一致性的快照中才能進行的(整個枚舉過程中子系統(tǒng)不會再出現(xiàn)根結(jié)點集合的對象引用關(guān)系的變化)
可作為GC Roots的節(jié)點主要在全局性的引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文(例如 棧幀中的本地變量表)中堕义。但是盡管我們目標明確,但是查找過程要做到高效并不是一件容易的事情脆栋。因為隨著Java應(yīng)用越來越大胳螟,光是方法去的大小就常有數(shù)百上千兆,每次都從這邊開始查找筹吐,無疑是一個耗時的操作糖耸。
此時就用到了個OopMap來記錄對象引用(這樣就不需要每次都從方法區(qū)開始找了)
OopMap在類加載動作完成之后,HotSpot就可以把對象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)給計算出來
安全點
但是要知道丘薛,不是每條指令都生成對應(yīng)的OopMap嘉竟,只在特定位置生成對應(yīng)的OopMap,這些位置就被稱為安全點(SafePoint)(安全點太少會導(dǎo)致收集器等待時間過程,太多又回增大運行時的內(nèi)存負荷)
可以作為安全點的幾個地方:方法調(diào)用舍扰、循環(huán)跳轉(zhuǎn)倦蚪、異常跳轉(zhuǎn)可以作為安全點
安全點=>具有讓程序長時間執(zhí)行的特征=>最明顯的特征就是指令序列的復(fù)用,即上述的幾個安全點
但是在我們進行垃圾回收边苹,并不是所有的線程都會處于安全點陵且。此時有兩種方法可以解決這個問題
- 搶先式中斷:不需要線程執(zhí)行代碼配合,在GC時个束,系統(tǒng)讓所有的用戶線程全部中斷如果線程沒有到安全點慕购,則恢復(fù)線程執(zhí)行,知道線程執(zhí)行到安全點目前幾乎沒有虛擬機使用這種方式
- 主動式中斷:虛擬機會設(shè)置一個標志位茬底,每個線程在執(zhí)行過程中會不斷地主動去輪詢這個標識位一旦標志位true沪悲,線程就會在自己最近的安全點主動中斷掛起
- 由于輪詢標識經(jīng)常出現(xiàn),需要考慮指令的高效阱表。HotSpot使用內(nèi)存保護陷阱的方式殿如,當需要暫停用戶線程時,就將0x160100的內(nèi)存頁設(shè)置為不可讀
安全區(qū)域
剛剛上述說到的最爬,都是在工作中的線程涉馁。但是正常運行的JVM虛擬機,肯定不止這種情況(Running)的線程爱致,還有處于Sleep或者Blocked狀態(tài)的線程烤送,他們是無法響應(yīng)虛擬機的中斷請求的,無法走到安全點去掛起自己蒜鸡。此時就引入了個安全區(qū)域的概念
用戶線程進入安全區(qū)域時胯努,就會標識自己進入安全區(qū)域牢裳,那么在此期間虛擬機發(fā)起垃圾收集時逢防,就不會去管這些已經(jīng)聲明自己在安全區(qū)域中的線程要離開時,會檢查是否完成根結(jié)點的枚舉蒲讯,如果未完成則會一直等待忘朝,直到收到可以離開安全區(qū)域的信號為止
同時關(guān)于可達性算法,這里提一個概念判帮,三色標記法
三色標記法
- 白色:對象未被垃圾收集器訪問過
- 黑色:對象已被垃圾收集器訪問過局嘁,且這個對象的所有引用都已經(jīng)掃描過,它是安全存活的如果有其他對象引用指向了黑色對象晦墙,無須重新掃描一遍
- 灰色:標識對象已經(jīng)被垃圾收集器訪問過悦昵,但這個對象上至少存在一個引用還沒有被掃描過
三色標記法的兩個問題
問題的前提:用戶的線程與收集器并發(fā)的工作,在標記的時候晌畅,用戶的線程也會去修改引用關(guān)系但指。會出現(xiàn)兩個問題
- 原本應(yīng)該消亡的對象,被錯誤的標記為存活。(這個問題可以容忍棋凳,本身發(fā)生的概率不高拦坠,下次垃圾回收時再回收就可以了)
- 原本應(yīng)該存活的對象被標記為消亡(這就不能容忍了)
第二種問題發(fā)生的原因:
產(chǎn)生上述問題需要兩個前提條件
- 賦值器插入了一條或多條從黑色對象到白色對象的新引用
- 賦值器刪除了全部從灰色對象到該白色對象的直接或間接引用
增量更新(破壞第一個條件):(CMS用到了)
當黑色對象插入新的指向白色對象的引用關(guān)系時,就將這個新插入的引用記錄下來剩岳,等并發(fā)掃描結(jié)束之后贞滨,再將這些記錄過的引用關(guān)系中的黑色對象為根,重新掃描一次拍棕。
這可以簡化理解為晓铆,黑色對象一旦新插入了指向白色對象的引用之后,它就變回灰色對象了莫湘。
原始快照(破壞第二個條件):(G1用到了)
當灰色對象要刪除指向白色對象的引用關(guān)系時尤蒿,就將這個要刪 除的引用記錄下來,在并發(fā)掃描結(jié)束之后幅垮,再將這些記錄過的引用關(guān)系中的灰色對象為根腰池,重新掃描一次。
這也可以簡化理解為忙芒,無論引用關(guān)系刪除與否示弓,都會按照剛剛開始掃描那一刻的對象圖快照來進行搜索。
即:將刪除引用的白色對象作為根呵萨,重新掃描奏属,保證當前白色對象不會被誤刪。不好的地方就是這個白色對象如果沒有再被引用潮峦,也得等到下次垃圾收集時被回收(ps:這一塊僅是自己的理解囱皿,后續(xù)還需要求證)
分代收集理論
因為將Java堆劃分出不同的區(qū)域,所以才會有垃圾收集器每次只回收其中一個或某些部分的區(qū)域
為什么需要分代收集
如果一個區(qū)域內(nèi)的大多數(shù)對象是朝生夕滅忱嘹,難以熬過垃圾收集過程
那么將他們集中在一起嘱腥,每次只需要考慮需要保留的少量存活對象而不是去標記那些大量需要回收的對象,
就可以以較低的成本回收大量的空間
同時拘悦,將那些難以回收的對象統(tǒng)一放在一個區(qū)域中齿兔,就可以以較低的頻率去回收這塊區(qū)域,
兼顧了垃圾收集的時間開銷和內(nèi)存空間的有效利用
相關(guān)名詞
Partial GC(部分收集)才有了 Minor GC/Young GC(新生代收集)础米、Major GC/Old GC(老年代收集)分苇、Full GC(整堆收集)、Mixed GC(收集整個新生代以及部分老年代屁桑,只有G1收集器有這樣的行為)
也才能發(fā)展出 “標記-復(fù)制算法”医寿,“標記-清除算法”,“標記-整理算法”
跨代引用問題
新生代的對象可能會被老年代引用蘑斧,老年代的對象也有可能會被新生代引用
假如此時我們要回收新生代的對象靖秩,就需要去掃描整個老年代艾帐,這無疑是一個不合理的操作
此時會在新生代上建立一個全局的數(shù)據(jù)結(jié)構(gòu)(記憶集 Remembered Set),將老年代劃分成若干小塊盆偿,標記處老年代的那一塊內(nèi)存存在跨代引用柒爸。然后在MinorGC的時候,只需要把包含了跨代引用的小塊內(nèi)存里面的對象假如到GC Roots進行掃描即可
關(guān)于記憶集事扭,存在三種精度
- 字長精度:每個記錄精確到一個機器字長(就是處理器的尋址位數(shù)捎稚,如常見的32位或64位,這個 精度決定了機器訪問物理內(nèi)存地址的指針長度)求橄,該字包含跨代指針今野。
- 對象精度:每個記錄精確到一個對象,該對象里有字段含有跨代指針罐农。
- 卡精度:每個記錄精確到一塊內(nèi)存區(qū)域条霜,該區(qū)域內(nèi)有對象含有跨代指針。
目前我們用到的最多的一種就是卡表涵亏≡姿卡表(Card Table)是卡精度的一種實現(xiàn)方式
卡表中的么一個元素都對應(yīng)著其標識的內(nèi)存區(qū)域中一塊特定大小的內(nèi)存塊,這個 內(nèi)存塊被稱作“卡頁”(Card Page)
一個卡頁的內(nèi)存中通常不止一個對象气筋,只要一個卡頁內(nèi)有一個(或多個)對象存在跨代指針拆内,則就在對應(yīng)卡表的數(shù)組元素的值標識位1,代表這個元素變臟宠默。(GC時只需要篩選出卡表中變臟的元素麸恍,然后將其加入到GC Roots中一并掃描)
此處HotSpot使用寫屏障來維護卡表。(可以使用類比的思想搀矫,AOP的Around來看待寫屏障維護卡表的操作)在引用對象的賦值會產(chǎn)生一個環(huán)繞(Around)通知抹沪。在賦值前的部分的寫屏障叫作寫前屏障(Pre-Write Barrier),在賦值后的則叫作寫后屏障(Post-Write Barrier)瓤球。HotSpot虛擬機的許多收集器中都有使用到寫屏障融欧,但直 至G1收集器出現(xiàn)之前,其他收集器都只用到了寫后屏障冰垄。
垃圾回收算法
- 標記-清除算法(標記過程也會有STW蹬癌,但是標記一般很快)(HotSpot的CMS收集器权她,關(guān)注延遲)
- 標記-復(fù)制算法
- 標記-整理算法(移動必須要STW虹茶,對象越多越大耗時越長)(HotSpot的ParallelScavenge收集器,關(guān)注吞吐)
是否移動對象都存在弊端隅要,移動則是在回收對象時復(fù)雜蝴罪,不移動則是在內(nèi)存分配時復(fù)雜。相比來說步清,內(nèi)存分配和訪問的頻率會比回收高很多