JVM垃圾回收(下)
接著上一篇,介紹完了 JVM 中識別需要回收的垃圾物件之後,這一篇我們來說說 JVM 是如何進行垃圾回收。
首先要在這裡介紹一下80/20 法則
:
約僅有20%的變因操縱著80%的局面。也就是說:所有變數中,最重要的僅有20%,雖然剩餘的80%佔了多數,控制的範圍卻遠低於“關鍵的少數”。
Java 物件的生命週期也滿足也這樣的定律,即大部分的 Java 物件只存活一小段時間,而存活下來的小部分 Java 物件則會存活很長一段時間。
因此,這也就造就了 JVM 中分代回收
的思想。簡單來說,就是將堆空間劃分為兩代,分別叫做新生代
和老年代
。新生代用來儲存新建的物件。當物件存活時間夠長時,則將其移動到老年代。
這樣也就可以讓 JVM 給不同代使用不同的回收演算法。
對於新生代,我們猜測大部分的 Java 物件只存活一小段時間,那麼便可以頻繁地採用耗時較短的垃圾回收演算法,讓大部分的垃圾都能夠在新生代被回收掉。
對於老年代,我們猜測大部分的垃圾已經在新生代中被回收了,而在老年代中的物件有大機率會繼續存活。當真正觸發針對老年代的回收時,則代表這個假設出錯了,或者堆的空間已經耗盡了。此時,JVM 往往需要做一次全堆掃描,耗時也將不計成本。(當然,現代的垃圾回收器都在併發收集的道路上發展,來避免這種全堆掃描的情況。)
那麼,我們先來看看 JVM 中堆究竟是如何劃分的。
堆劃分
按照上文所述,JVM 將堆劃分為新生代和老年代,其中,新生代又被劃分為 Eden 區,以及兩個大小相同的 Survivor 區。
通常來說,當我們呼叫 new 指令時,它會在 Eden 區中劃出一塊作為儲存物件的記憶體。由於堆空間是執行緒共享的,因此直接在這裡邊劃空間是需要進行同步的。否則,將有可能出現兩個物件共用一段記憶體的事故。
JVM 的解決方法是為每個執行緒預先申請一段連續的堆空間,並且只允許每個執行緒在自己申請過的堆空間中建立物件,如果申請的堆空間被用完了,那麼再繼續申請即可,這也就是 TLAB(Thread Local Allocation Buffer,對應虛擬機器引數 -XX:+UseTLAB,預設開啟)。
此時,如果執行緒操作涉及到加鎖,則該執行緒需要維護兩個指標(實際上可能更多,但重要也就兩個),一個指向 TLAB 中空餘記憶體的起始位置,一個則指向 TLAB 末尾。
接下來的 new 指令,便可以直接透過指標加法(bump the pointer)來實現,即把指向空餘記憶體位置的指標加上所請求的位元組數。
如果加法後空餘記憶體指標的值仍小於或等於指向末尾的指標,則代表分配成功。否則,TLAB 已經沒有足夠的空間來滿足本次新建操作。這個時候,便需要當前執行緒重新申請新的 TLAB。
那有沒有可能出現申請不到的情況呢?有的,這個時候就會觸發Minor GC
了。
Minor GC
所謂 Minor GC,就是指:
當 Eden 區的空間耗盡時,JVM 會進行一次 Minor GC,來收集新生代的垃圾。存活下來的物件,則會被送到 Survivor 區。
上文提到,新生代共有兩個 Survivor 區,我們分別用 from 和 to 來指代。其中 to 指向的 Survivior 區是空的。
當發生 Minor GC 時,Eden 區和 from 指向的 Survivor 區中的存活物件會被複制到 to 指向的 Survivor 區中,然後交換 from 和 to 指標,以保證下一次 Minor GC 時,to 指向的 Survivor 區還是空的。
JVM 會記錄 Survivor 區中每個物件一共被來回複製了幾次。如果一個物件被複制的次數為 15(對應虛擬機器引數 -XX:+MaxTenuringThreshold),那麼該物件將被晉升(promote)至老年代。
另外,如果單個 Survivor 區已經被佔用了 50%(對應虛擬機器引數 -XX:TargetSurvivorRatio),那麼較高複製次數的物件也會被晉升至老年代。
總而言之,當發生 Minor GC 時,我們應用了標記 - 複製
演算法,將 Survivor 區中的老存活物件晉升到老年代,然後將剩下的存活物件和 Eden 區的存活物件複製到另一個 Survivor 區中。理想情況下,Eden 區中的物件基本都死亡了,那麼需要複製的資料將非常少,因此採用這種標記 - 複製
演算法的效果極好。
Minor GC 的另外一個好處是不用對整個堆進行垃圾回收。但是,它卻有一個問題,那就是老年代中的物件可能引用新生代的物件。也就是說,在標記存活物件的時候,我們需要掃描老年代中的物件。如果該物件擁有對新生代物件的引用,那麼這個引用也會被作為 GC Roots。這樣一來,豈不是又做了一次全堆掃描呢?
為了避免掃描全堆,JVM 引入了名為卡表
的技術,大致地標出可能存在老年代到新生代引用的記憶體區域。有興趣的朋友可以去詳細瞭解一下,這裡限於篇幅,就不具體介紹了。
Full GC
那什麼時候會發生Full GC
呢?針對不同的垃圾收集器,Full GC 的觸發條件可能不都一樣。按 HotSpot VM 的 serial GC 的實現來看,觸發條件是:
當準備要觸發一次 Minor GC 時,如果發現統計資料說之前 Minor GC 的平均晉升大小比目前老年代剩餘的空間大,則不會觸發 Minor GC 而是轉為觸發 Full GC。
因為 HotSpot VM 的 GC 裡,除了垃圾回收器 CMS 能單獨收集老年代之外,其他的 GC 都會同時收集整個堆,所以不需要事先準備一次單獨的 Minor GC。
垃圾回收
基礎的回收方式有三種:清除
、壓縮
、複製
,接下來讓我們來一一瞭解一下。
清除
所謂清除,就是把死亡物件所佔據的記憶體標記為空閒記憶體,並記錄在一個空閒列表之中。當需要新建物件時,記憶體管理模組便會從該空閒列表中尋找空閒記憶體,並劃分給新建的物件。
其原理十分簡單,但是有兩個缺點:
- 會造成記憶體碎片。由於 JVM 的堆中物件必須是連續分佈的,因此可能出現總空閒記憶體足夠,但是無法分配的極端情況。
- 分配效率較低。如果是一塊連續的記憶體空間,那麼我們可以透過指標加法(pointer bumping)來做分配。而對於空閒列表,JVM 則需要逐個訪問空閒列表中的項,來查詢能夠放入新建物件的空閒記憶體。
壓縮
所謂壓縮,就是把存活的物件聚集到記憶體區域的起始位置,從而留下一段連續的記憶體空間。
這種做法能夠解決記憶體碎片化的問題,但代價是壓縮演算法的效能開銷,因此分配效率問題依舊沒有解決。
複製
所謂複製,就是把記憶體區域平均分為兩塊,分別用兩個指標 from 和 to 來維護,並且只是用 from 指標指向的記憶體區域來分配記憶體。當發生垃圾回收時,便把存活的物件複製到 to 指標所指向的記憶體區域中,並且交換 from 指標和 to 指標的內容。
這種回收方式同樣能夠解決記憶體碎片化的問題,但是它的缺點也極其明顯,即堆空間的使用效率極其低下。
具體垃圾收集器
針對新生代
的垃圾回收器共有三個:Serial ,Parallel Scavenge 和 Parallel New。這三個採用的都是標記 - 複製
演算法。
其中,Serial 是一個單執行緒的,Parallel New 可以看成是 Serial 的多執行緒版本,Parallel Scavenge 和 Parallel New 類似,但更加註重吞吐率。此外,Parallel Scavenge 不能與 CMS 一起使用。
針對老年代
的垃圾回收器也有三個:Serial Old ,Parallel Old 和 CMS。
Serial Old 和 Parallel Old 都是標記 - 壓縮
演算法。同樣,前者是單執行緒的,而後者可以看成前者的多執行緒版本。
CMS 採用的是標記 - 清除
演算法,並且是併發的。除了少數幾個操作需要 STW(Stop the world) 之外,它可以在應用程式執行過程中進行垃圾回收。在併發收集失敗的情況下,JVM 會使用其他兩個壓縮型垃圾回收器進行一次垃圾回收。由於 G1 的出現,CMS 在 Java 9 中已被廢棄。
G1(Garbage First)是一個橫跨新生代和老年代的垃圾回收器。實際上,它已經打亂了前面所說的堆結構,直接將堆分成極其多個區域。每個區域都可以充當 Eden 區、Survivor 區或者老年代中的一個。它採用的是標記 - 壓縮
演算法,而且和 CMS 一樣都能夠在應用程式執行過程中併發地進行垃圾回收。
G1 能夠針對每個細分的區域來進行垃圾回收。在選擇進行垃圾回收的區域時,它會優先回收死亡物件較多的區域。這也是 G1 名字的由來。
總結
這篇文章主要講述的是 JVM 中具體的垃圾回收方法,從物件的生存規律,引出回收方法,結合多執行緒的特點,逐步最佳化,最終產生了我們現在所能知道各種垃圾收集器。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2894/viewspace-2823952/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- [JVM]垃圾回收JVM
- JVM垃圾回收JVM
- JVM垃圾回收概述JVM
- JVM垃圾回收器JVM
- JVM - 垃圾回收概述JVM
- JVM 垃圾回收演算法和垃圾回收器JVM演算法
- [效能][JVM]jvm垃圾回收機制JVM
- JVM系列(六) – JVM垃圾回收器JVM
- JVM系列(六) - JVM垃圾回收器JVM
- jvm 自動垃圾回收JVM
- JVM垃圾回收機制JVM
- JVM-垃圾回收篇JVM
- JVM 中的垃圾回收JVM
- 淺談JVM垃圾回收JVM
- JVM垃圾回收歷險JVM
- JVM 垃圾回收機制JVM
- 關於JVM的垃圾回收JVM
- jvm的垃圾回收機制JVM
- JVM垃圾回收演算法JVM演算法
- 淺談JVM與垃圾回收JVM
- 帶你掌握JVM垃圾回收JVM
- 再看JVM:垃圾回收那些事JVM
- 理解JVM(七):垃圾回收器JVM
- jvm(4)---垃圾回收(哪些物件可以被回收)JVM物件
- JVM系列(五) - JVM垃圾回收演算法JVM演算法
- 垃圾回收_下
- 談談 JVM 垃圾回收機制JVM
- JVM(九):垃圾回收演算法JVM演算法
- 深入探究JVM之垃圾回收器JVM
- 更快的JVM垃圾回收器:ShenandoahJVMNaN
- JVM垃圾回收機制入門JVM
- 【JVM】垃圾回收器總結(2)——七種垃圾回收器型別JVM型別
- JVM垃圾回收器、記憶體分配與回收策略JVM記憶體
- jvm有哪些垃圾回收演算法JVM演算法
- JVM記憶體管理和垃圾回收JVM記憶體
- 秒懂JVM的垃圾回收機制JVM
- 《深入理解JVM》10-垃圾回收JVM
- JVM - 方法區(永久代)的垃圾回收JVM