1. 程式人生 > >JVM垃圾回收(下)

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。

垃圾回收

基礎的回收方式有三種:清除壓縮複製,接下來讓我們來一一瞭解一下。

清除

所謂清除,就是把死亡物件所佔據的記憶體標記為空閒記憶體,並記錄在一個空閒列表之中。當需要新建物件時,記憶體管理模組便會從該空閒列表中尋找空閒記憶體,並劃分給新建的物件。

其原理十分簡單,但是有兩個缺點:

  1. 會造成記憶體碎片。由於 JVM 的堆中物件必須是連續分佈的,因此可能出現總空閒記憶體足夠,但是無法分配的極端情況。
  2. 分配效率較低。如果是一塊連續的記憶體空間,那麼我們可以通過指標加法(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 中具體的垃圾回收方法,從物件的生存規律,引出回收方法,結合多執行緒的特點,逐步優化,最終產生了我們現在所能知道各種垃圾收集器。

有興趣的話可以訪問我的部落格或者關注我的公眾號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/

相關推薦

12 JVM 垃圾回收

-o 不用 通過 字節數 water 直接 重新 標記 被占用 Java 虛擬機的堆劃分 Java 虛擬機將堆劃分為新生代和老年代。其中新生代又被劃分為 Eden 區,以及兩個大小相同的 Survivor 區。 默認情況下,Java 虛擬機采取一種動態分配的策略,根據對象生

JVM垃圾回收

接著上一篇,介紹完了 JVM 中識別需要回收的垃圾物件之後,這一篇我們來說說 JVM 是如何進行垃圾回收。 首先要在這裡介紹一下80/20 法則: 約僅有20%的變因操縱著80%的局面。也就是說:所有變數中,最重要的僅有20%,雖然剩餘的80%佔了多數,控制的範圍卻遠低於“關鍵的少數”。 Java 物件

JVM——垃圾回收GC

.text 永久代 lines script from nes ng- code addclass GC簡單介紹 java語言執行在java虛擬機(jvm)上。為了解決有限的空間和性能的保證這個矛盾體,jvm所具備的GC能力。能夠有效的清除不用的對象。

深入拆解虛擬機器垃圾回收

堆記憶體劃分 Java虛擬機器將堆分為新生代和老年代,並且對不同代採用不同的垃圾回收演算法。其中,新生代分為Eden區和兩個大小一致的Survivor區,並且其中一個Survivor區是空的 Minor GC (1)在只針對新生代的Minor GC中,Eden區和非S空

JVM垃圾回收- GC算法:實現1

並行 ctime 配置 使用情況 ava 第一個 中標 算法 bsp GC算法:實現 上面我們介紹了GC算法中的核心概念,接下來我們看一下JVM裏的具體實現。首先必須了解的一個重要的事實是:對於大部分的JVM來說,兩種不同的GC算法是必須的,一個是清理Young Gene

JVM 垃圾回收GC機制

目錄 一、背景 二、 哪些記憶體需要回收? 1、引用計數演算法 2 、可達性分析演算法 三、 四種引用狀態 1、強引用 2、軟引用 3、弱引用 4、虛引用 物件死亡(被回收)前的最後一次掙扎 方法區如何判斷是否需要回收 四、垃圾收集

JVM垃圾回收演算法

1、回收演算法 標記回收演算法(Mark and Sweep GC) 從GC Roots集合開始,將記憶體整個遍歷一次,保留所有可以被GC Roots直接或間接引用到的物件,而剩下的物件都當作垃圾對待並回收,這個演算法需要中斷程序內其它元件的執行並

JVM垃圾回收

Java 中的垃圾回收,常常是由 JVM 幫我們做好的。雖然這節省了大家很多的學習的成本,提高了專案的執行效率,但是當專案變得越來越複雜,使用者量越來越大時,還是需要我們懂得垃圾回收機制,這樣也能進行更深一步的優化。 辨別物件存亡 垃圾回收( Garbage Collection,以下簡稱 GC ),從字面

JVM垃圾回收GC

### JVM垃圾回收(GC) #### 1. 判斷物件是否可以被回收 - 引用計數法:每個物件有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,但**無法解決物件相互迴圈引用的問題**。 ```java // 迴圈引用 Node a=new N

JVM (四)--垃圾回收

程式計數器、虛擬機器棧、本地方法棧這三個區域屬於執行緒私有,只存在於執行緒的生命週期內,執行緒結束之後也會消失,因此,不需要對這三個區域進行垃圾回收。垃圾回收主要針對方法區和Java堆進行。 一、判斷一個物件是否存活 1、引用計數演算法 給物件新增一個引用計數器,當物件增加一個引用時

Java垃圾回收GC機制詳解

nbsp 引用計數 維護 png 對象 最新 新的 com 前沿 垃圾回收算法有兩種,根據不同的虛擬機策略不同 1、引用計數法 2、可達性分析法 由於我們平常使用的hotspot虛擬機用的是第二種。 那哪些是可達的呢? 這個算法的基本思想是通過一系列稱為“GC Roots”

各種垃圾回收

垃圾回收算法 告訴 策略 trac 銷毀 pin 完整 多線程 概念 1. 垃圾回收的意義  在C++中,對象所占的內存在程序結束運行之前一直被占用,在明確釋放之前不能分配給其它對象;而在Java中,當沒有對象引用指向原先分配給某個對象的內存時,該內存便成為垃圾。JVM的一

深入拆解虛擬機器垃圾回收

引用計數法 (1)它的做法是為每個物件新增一個引用計數器,用來統計指向該物件的引用個數。一旦某個物件 的引用計數器為0,則說明該物件已經死亡,便可以被回收了。 (2)具體實現:如果有一個引用,被賦值為某一個物件,那麼該物件的引用計數器+1。如果指向某一個物件的引用,被賦值為其他值

Java虛擬機器 :Java垃圾回收GC機制詳解

轉自:http://www.importnew.com/28413.html 哪些記憶體需要回收? 哪些記憶體需要回收是垃圾回收機制第一個要考慮的問題,所謂“要回收的垃圾”無非就是那些不可能再被任何途徑使用的物件。那麼如何找到這些物件? 1、引用計數法 這個演算法的實現是,給物件中新

JVM03------垃圾收集

一. 什麼是GC Java與C語言相比的一個優勢是,可以通過自己的JVM自動分配和回收記憶體空間。 垃圾回收機制是由垃圾收集器Garbage Collection來實現的,GC是後臺一個低優先順序的守護程序。在記憶體中低到一定限度時才會自動執行,因此垃圾回收的時間是不確定的。 為何要這樣設計:因為GC也

Python3 垃圾回收GC

1. 小整數物件池 整數在程式中的使用非常廣泛,Python為了優化速度,使用了小整數物件池, 避免為整數頻繁申請和銷燬記憶體空間。 Python 對小整數的定義是 [-5, 257) 這些整數物件是提前建立好的,不會被垃圾回收。在一個 Python 的程式中,所有位於這個範圍內的整數使用

JAVA虛擬機器之一:垃圾回收GC機制

引言 java對於其它語言(c/c++)來說,建立一個物件使用後,不用顯式的delete/free,且能在一定程度上保證系統記憶體資源及時回收,這要功歸於java的自動垃圾回收機制(Garbage Collection,GC),但也是因為自動回收機制存在,一旦系統內洩漏或存

淺談java垃圾回收

本文簡述java垃圾回收機制自己的一點理解,希望能對java學習的朋友有一點幫助,謝謝; 先了解下JVM: JVM百度百科詳情https://baike.so.com/doc/1063579-1125177.html JVM是Java Virtual Machine(Java虛擬機器)的

對比Ruby和Python的垃圾回收2:代式垃圾回收機制

上週,我根據之前在RuPy上做的一個名為“Visualizing Garbage Collection in Ruby and Python.”的報告寫了這篇文章的上半部分。在上篇中,我解釋了標準Ruby(也被稱為Matz的Ruby直譯器或是MRI)是如何使用名為

Java中垃圾回收gc問題

以下哪項陳述是正確的? A. 垃圾回收執行緒的優先順序很高,以保證不再 使用的記憶體將被及時回收 B. 垃圾收集允許程式開發者明確指定釋放 哪一個物件 C. 垃圾回收機制保證了JAVA程式不會出現 記憶體溢位 D. 進入”Dead”狀態的執行緒將被垃圾回