1. 程式人生 > >老生常談Java虛擬機器垃圾回收機制(必看篇)

老生常談Java虛擬機器垃圾回收機制(必看篇)

二、垃圾收集

垃圾收集主要是針對堆和方法區進行。

程式計數器、虛擬機器棧和本地方法棧這三個區域屬於執行緒私有的,只存在於執行緒的生命週期內,執行緒結束之後也會消失,因此不需要對這三個區域進行垃圾回收。

 

判斷一個物件是否可被回收

1. 引用計數演算法

給物件新增一個引用計數器,當物件增加一個引用時計數器加 1,引用失效時計數器減 1。引用計數為 0 的物件可被回收。

兩個物件出現迴圈引用的情況下,此時引用計數器永遠不為 0,導致無法對它們進行回收。

正因為迴圈引用的存在,因此 Java 虛擬機器不使用引用計數演算法。

public class ReferenceCountingGC {

    public Object instance = null;

    public static void main(String[] args) {
        ReferenceCountingGC objectA = new ReferenceCountingGC();
        ReferenceCountingGC objectB = new ReferenceCountingGC();
        objectA.instance = objectB;
        objectB.instance = objectA;
    }
}

2. 可達性分析演算法

通過 GC Roots 作為起始點進行搜尋,能夠到達到的物件都是存活的,不可達的物件可被回收。

Java 虛擬機器使用該演算法來判斷物件是否可被回收,在 Java 中 GC Roots 一般包含以下內容:

  • 虛擬機器棧中區域性變量表中引用的物件
  • 本地方法棧中 JNI 中引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中的常量引用的物件

 

3. 方法區的回收

因為方法區主要存放永久代物件,而永久代物件的回收率比新生代低很多,因此在方法區上進行回收價效比不高。

主要是對常量池的回收對類的解除安裝

在大量使用反射、動態代理、CGLib 等 ByteCode 框架、 動態生成 JSP 以及 OSGi 這類頻繁自定義 ClassLoader 的場景都需要虛擬機器具備類解除安裝功能,以保證不會出現記憶體溢位。

類的解除安裝條件很多,需要滿足以下三個條件,並且滿足了也不一定會被解除安裝:

  • 該類所有的例項都已經被回收,也就是堆中不存在該類的任何例項
  • 載入該類的 ClassLoader 已經被回收
  • 該類對應的 Class 物件沒有在任何地方被引用,也就無法在任何地方通過反射訪問該類方法。

可以通過 -Xnoclassgc 引數來控制是否對類進行解除安裝。

4. finalize()

finalize() 類似 C++ 的解構函式,用來做關閉外部資源等工作。但是 try-finally 等方式可以做的更好, 並且該方法執行代價高昂,不確定性大,無法保證各個物件的呼叫順序,因此最好不要使用。

當一個物件可被回收時,如果需要執行該物件的 finalize() 方法, 那麼就有可能在該方法中讓物件重新被引用,從而實現自救。 自救只能進行一次,如果回收的物件之前呼叫了 finalize() 方法自救,後面回收時不會呼叫 finalize() 方法。

引用型別

無論是通過引用計算演算法判斷物件的引用數量,還是通過可達性分析演算法判斷物件是否可達, 判定物件是否可被回收都與引用有關。

Java 提供了四種強度不同的引用型別。

1. 強引用

被強引用關聯的物件不會被回收

使用 new 一個新物件的方式來建立強引用。

Object obj = new Object();

2. 軟引用

被軟引用關聯的物件只有在記憶體不夠的情況下才會被回收

使用 SoftReference 類來建立軟引用。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;  // 使物件只被軟引用關聯

3. 弱引用

被弱引用關聯的物件一定會被回收,也就是說它只能存活到下一次垃圾回收發生之前。

使用 WeakReference 類來實現弱引用。

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;

4. 虛引用

又稱為幽靈引用或者幻影引用。一個物件是否有虛引用的存在, 完全不會對其生存時間構成影響,也無法通過虛引用取得一個物件。

為一個物件設定虛引用關聯的唯一目的就是能在這個物件被回收時收到一個系統通知

使用 PhantomReference 來實現虛引用。

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;

垃圾收集演算法

1. 標記 - 清除

 

將存活的物件進行標記,然後清理掉未被標記的物件。

不足:

  • 標記和清除過程效率都不高;
  • 會產生大量不連續的記憶體碎片,導致無法給大物件分配記憶體。

2. 標記 - 整理

 

讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

3. 複製

 

將記憶體劃分為大小相等的兩塊,每次只使用其中一塊,當這一塊記憶體用完了就將還存活的物件複製到另一塊上面,然後再把使用過的記憶體空間進行一次清理。

主要不足是隻使用了記憶體的一半。

現在的商業虛擬機器都採用這種收集演算法來回收新生代,但是並不是將新生代劃分為大小相等的兩塊,而是分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的物件一次性複製到另一塊 Survivor 空間上,最後清理 Eden 和使用過的那一塊 Survivor。

HotSpot 虛擬機器的 Eden 和 Survivor 的大小比例預設為 8:1,保證了記憶體的利用率達到 90%。如果每次回收有多於 10% 的物件存活,那麼一塊 Survivor 空間就不夠用了,此時需要依賴於老年代進行分配擔保,也就是借用老年代的空間儲存放不下的物件。

4. 分代收集

現在的商業虛擬機器採用分代收集演算法,它根據物件存活週期將記憶體劃分為幾塊,不同塊採用適當的收集演算法。

一般將堆分為新生代和老年代。

  • 新生代使用:複製演算法
  • 老年代使用:標記 - 清除 或者 標記 - 整理 演算法

垃圾收集器

 

以上是 HotSpot 虛擬機器中的 7 個垃圾收集器,連線表示垃圾收集器可以配合使用。

  • 單執行緒與多執行緒:單執行緒指的是垃圾收集器只使用一個執行緒進行收集,而多執行緒使用多個執行緒;
  • 序列與並行:序列指的是垃圾收集器與使用者程式交替執行,這意味著在執行垃圾收集的時候需要停頓使用者程式;並行指的是垃圾收集器和使用者程式同時執行。除了CMS 和 G1之外,其它垃圾收集器都是以序列的方式執行。

1. Serial 收集器

 

Serial 翻譯為序列,也就是說它以序列的方式執行。

它是單執行緒的收集器,只會使用一個執行緒進行垃圾收集工作。

它的優點是簡單高效,對於單個 CPU 環境來說,由於沒有執行緒互動的開銷,因此擁有最高的單執行緒收集效率。

它是 Client 模式下的預設新生代收集器,因為在該應用場景下,分配給虛擬機器管理的記憶體一般來說不會很大。Serial 收集器收集幾十兆甚至一兩百兆的新生代停頓時間可以控制在一百多毫秒以內,只要不是太頻繁,這點停頓是可以接受的。

2. ParNew 收集器

 

它是 Serial 收集器的多執行緒版本。

是 Server 模式下的虛擬機器首選新生代收集器,除了效能原因外,主要是因為除了 Serial 收集器,只有它能與 CMS 收集器配合工作。

預設開啟的執行緒數量與 CPU 數量相同,可以使用 -XX:ParallelGCThreads 引數來設定執行緒數。

3. Parallel Scavenge 收集器

與 ParNew 一樣是多執行緒收集器。

其它收集器關注點是儘可能縮短垃圾收集時使用者執行緒的停頓時間,而它的目標是達到一個可控制的吞吐量,它被稱為“吞吐量優先”收集器。這裡的吞吐量指 CPU 用於執行使用者程式碼的時間佔總時間的比值。

停頓時間越短就越適合需要與使用者互動的程式,良好的響應速度能提升使用者體驗。而高吞吐量則可以高效率地利用 CPU 時間,儘快完成程式的運算任務,適合在後臺運算而不需要太多互動的任務。

縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,導致吞吐量下降。

可以通過一個開關引數開啟 GC 自適應的調節策略(GC Ergonomics), 就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 區的比例、晉升老年代物件年齡等細節引數了。 虛擬機器會根據當前系統的執行情況收集效能監控資訊, 動態調整這些引數以提供最合適的停頓時間或者最大的吞吐量。 自適應調節策略是 Parallel Scavenge 收集器和 ParNew 收集器的一個重要區別。

4. Serial Old 收集器

 

是 Serial 收集器的老年代版本,也是給 Client 模式下的虛擬機器使用,採用標記-整理演算法。如果用在 Server 模式下,它有兩大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 誕生以前)中與 Parallel Scavenge 收集器搭配使用。
  • 作為 CMS 收集器的後備預案,在併發收集發生 Concurrent Mode Failure 時使用。

5. Parallel Old 收集器

 

是 Parallel Scavenge 收集器的老年代版本,採用標記-整理演算法。

在注重吞吐量以及 CPU 資源敏感的場合,都可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。

6. CMS 收集器

 

CMS(Concurrent Mark Sweep),Mark Sweep 指的是標記 - 清除演算法

分為以下四個流程:

  • 初始標記:僅僅只是標記一下 GC Roots 能直接關聯到的物件,速度很快,需要停頓。
  • 併發標記:進行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓。
  • 重新標記:為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,需要停頓。
  • 併發清除:不需要停頓。

在整個過程中耗時最長的併發標記和併發清除過程中,收集器執行緒都可以與使用者執行緒一起工作,不需要進行停頓,具有併發收集、低停頓的優點。

具有以下缺點:

  • 吞吐量低:低停頓時間是以犧牲吞吐量為代價的,導致 CPU 利用率不夠高
  • 無法處理浮動垃圾,可能出現 Concurrent Mode Failure。浮動垃圾是指併發清除階段由於使用者執行緒繼續執行而產生的垃圾,這部分垃圾只能到下一次 GC 時才能進行回收。 由於浮動垃圾的存在,因此需要預留出一部分記憶體,意味著 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。如果預留的記憶體不夠存放浮動垃圾,就會出現 Concurrent Mode Failure,這時虛擬機器將臨時啟用 Serial Old 來替代 CMS。
  • 標記 - 清除演算法導致的空間碎片,往往出現老年代空間剩餘,但無法找到足夠大連續空間來分配當前物件,不得不提前觸發一次 Full GC。

CMS 已經在 JDK 9 中被標記為廢棄( deprecated )。

7. G1 收集器

G1(Garbage-First),它是一款面向服務端應用的垃圾收集器,在多 CPU 和大記憶體的場景下有很好的效能。HotSpot 開發團隊賦予它的使命是未來可以替換掉 CMS 收集器。

堆被分為新生代和老年代,其它收集器進行收集的範圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收

 

G1 把堆劃分成多個大小相等的獨立區域(Region),Region的大小是一致的,數值是在1M到32M位元組之間的一個2的冪值數,JVM會盡量劃分2048個左右、同等大小的Region,新生代和老年代不再物理隔離

 

通過引入 Region 的概念,從而將原來的一整塊記憶體空間劃分成多個的小空間,使得每個小空間可以單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預測的停頓時間模型成為可能。通過記錄每個 Region 垃圾回收時間以及回收所獲得的空間(這兩個值是通過過去回收的經驗獲得),並維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region。

每個 Region 都有一個 Remembered Set,用來記錄該 Region 物件的引用物件所在的 Region。通過使用 Remembered Set,在做可達性分析的時候就可以避免全堆掃描。

 

如果不計算維護 Remembered Set 的操作,G1 收集器的運作大致可劃分為以下幾個步驟:

  • 初始標記
  • 併發標記
  • 最終標記:為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器將這段時間物件變化記錄線上程的 Remembered Set Logs 裡面,最終標記階段需要把 Remembered Set Logs 的資料合併到 Remembered Set 中。這階段需要停頓執行緒,但是可並行執行。
  • 篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據使用者所期望的 GC 停頓時間來制定回收計劃。此階段其實也可以做到與使用者程式一起併發執行,但是因為只回收一部分 Region,時間是使用者可控制的,而且停頓使用者執行緒將大幅度提高收集效率。

從 GC 演算法的角度, G1 選擇的是複合演算法,可以簡化理解為:

  • 在新生代,G1採用的仍然是並行的複製演算法,所以同樣會發生Stop-The-World的暫停。
  • 在老年代,大部分情況下都是併發標記,而整理(Compact)則是和新生代GC時捎帶進行,並且不是整體性的整理,而是增量進行的。

具備如下特點:

  • 空間整合:整體來看是基於“標記 - 整理”演算法實現的收集器,從區域性(兩個 Region 之間)上來看是基於“複製”演算法實現的,這意味著執行期間不會產生記憶體空間碎片。
  • 可預測的停頓:能讓使用者明確指定在一個長度為 M 毫秒的時間片段內,消耗在 GC 上的時間不得超過 N 毫秒。

目前尚處於開發中的 JDK 11, JDK 又增加了兩種全新的 GC 方式,分別是:

  • Epsilon GC,簡單說就是個不做垃圾收集的GC,似乎有點奇怪,有的情況下,例如在進行效能測試的時候,可能需要明確判斷GC本身產生了多大的開銷,這就是其典型應用場景。
  • ZGC,這是Oracle開源出來的一個超級GC實現,具備令人驚訝的擴充套件能力,比如支援T bytes級別的堆大小,並且保證絕大部分情況下,延遲都不會超過10 ms。雖然目前還處於實驗階段,僅支援 Linux 64 位的平臺,但其已經表現出的能力和潛力都非常令人期待。

相關推薦

老生常談Java虛擬機器垃圾回收機制()

二、垃圾收集 垃圾收集主要是針對堆和方法區進行。 程式計數器、虛擬機器棧和本地方法棧這三個區域屬於執行緒私有的,只存在於執行

Java虛擬垃圾回收機制

收集器 空間足 沒有 內存區域 區別 run 虛引用 應用 運行 在Java虛擬機中,對象和數組的內存都是在堆中分配的,垃圾收集器主要回收的內存就是再堆內存中。如果在Java程序運行過程中,動態創建的對象或者數組沒有及時得到回收,持續積累,最終堆內存就會被占滿,導致OOM。

JAVA虛擬垃圾回收機制JAVA排錯三劍客

jvm 垃圾 回收機制 一、Java虛擬機邏輯回收機制1、Java垃圾回收器 Java垃圾回收器是Java虛擬機(JVM)的三個重要模塊(另外兩個是解釋器和多線程機制)之一,為應用程序提供內存的自動分配(Memory Allocation)、自動回收(Garbage Collect)

資料結構和虛擬機器垃圾回收機制初識

馮諾依曼體系中,計算機五大部件分為輸入裝置、儲存器、輸出裝置、控制器、運算器。其中運算器和控制器組成CPU。資料互動流程如下圖: 標題 其中暫存器、快取、記憶體都是斷電即失,暫存器以及快取在資料互動的速度雖然快,但是空間太小,所以記憶體依然

java虛擬機器垃圾回收演算法

引用計數法: 原理:對於物件A,只要任意的物件引用了A,則A的引用計數器加一。當引用失效時,引用計數器減一。引用計數器的值為0時,物件A不可使用,回收。 缺點:1.無法處理迴圈問題。如果A引用了B,B同時引用了A。但A,B都不被其他任何物件引用。那麼A,B都是不可達的。那

Java虛擬機器垃圾回收(三) 7種垃圾收集器  應用場景

Java虛擬機器垃圾回收(三) 7種垃圾收集器  主要特點 應用場景 設定引數 基本執行原理        下面先來了解HotSpot虛擬機器中的7種垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Paral

java虛擬機器——垃圾回收

Serial收集器: serial收集器是最基本的發展最悠久的收集器,這是一個單執行緒收集器,它只會使用一個CPU,一個收集執行緒去完成垃圾收集工作,更重要的是它進行垃圾收集工作時必須暫停所有的其他執行緒,直到它收集結束。暫停所有執行緒,stop the  world,在使

java虛擬機器垃圾回收被誤解的7件事

對Java垃圾回收最大的誤解是什麼?它實際又是什麼樣的呢? 當 我還是小孩的時候,父母常說如果你不好好學習,就只能去掃大街了。但他們不知道的是,清理垃圾實際上是很棒的一件事。可能這也是即使在Java的世界中, 同樣有很多開發者對GC演算法產生誤解的原因——包括它們怎樣工作、

Java虛擬機器垃圾回收(三) 7種垃圾收集器:主要特點 應用場景 設定引數 基本執行原理

Java虛擬機器垃圾回收(三) 7種垃圾收集器  主要特點 應用場景 設定引數 基本執行原理        下面先來了解HotSpot虛擬機器中的7種垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Paralle

Java虛擬機器垃圾回收相關知識點全梳理(上)

一、前言 筆者最近在複習JVM的知識,本著記錄分享的精神,整理下學習Java虛擬機器垃圾回收相關知識點,由於整個垃圾回收內容比較

Java虛擬機器垃圾回收相關知識點全梳理(下)

一、前言 上一篇文章《Java虛擬機器垃圾回收相關知識點全梳理(上)》我整理分享了JVM執行時資料區域的劃分,垃圾判定演算法以及垃

Java 虛擬機器垃圾收集機制詳解

> 本文摘自深入理解 Java 虛擬機器第三版 ## 垃圾收集發生的區域 之前我們介紹過 Java 記憶體執行時區域的各個部分,其中程式計數器、虛擬機器棧、本地方法棧三個區域隨執行緒共存亡。棧中的每一個棧幀分配多少記憶體基本上在類結構確定下來時就已知,因此這幾個區域的記憶體分配和回收都具有確定性,

【譯】Java SE 14 Hotspot 虛擬機器垃圾回收調優指南

原文連結:[HotSpot Virtual Machine Garbage Collection Tuning Guide](https://docs.oracle.com/en/java/javase/14/gctuning/introduction-garbage-collection-tuning.ht

java中存在垃圾回收機制,但是還會有內存泄漏的問題,原因是

java 自己 data .so 這樣的 即使 垃圾 ref stack 答案是肯定的,但不能拿這一句回答面試官的問題。分析:JAVA是支持垃圾回收機制的,在這樣的一個背景下,內存泄露又被稱為“無意識的對象保持”。如果一個對象引用被無意識地保留下來,那麽垃圾回收器不僅不會處

Java分代垃圾回收機制:年輕代/年老代/持久代(轉)

進行 目標 targe 先後 技術分享 靜態文件 運行 you 頻繁 虛擬機中的共劃分為三個代:年輕代(Young Generation)、年老點(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,

java虛擬垃圾回收被誤解的7件事

均值 發的 影響 per 閱讀 內存 日誌配置 進一步 組件 當 我還是小孩的時候,父母常說如果你不好好學習,就只能去掃大街了。但他們不知道的是,清理垃圾實際上是很棒的一件事。可能這也是即使在Java的世界中, 同樣有很多開發者對GC算法產生誤解的原因——包括它們怎樣工作、

java虛擬機器類載入機制學習

1、什麼是類的載入 類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構。類的載入的最終產品是位於堆區中的Class物件,Class物件封裝了類在方法區內的資料結構

Java虛擬機器類載入機制經典案例

package io.lgxkdream.test; class Father { static Father f = new Father(); static { System.out.println("father-1"); } { System.out.println("

Java:JVM垃圾回收機制

JVM垃圾回收機制 提到Java垃圾回收機制就不得不提到一個方法: system.gc() 用於呼叫垃圾收集器,在呼叫時垃圾收集器將執行以回收未使用的記憶體空間,它將嘗試釋放被丟棄物件所佔用的空間。 作為程式設計師有必要了解gc方

深入理解Java虛擬機器--垃圾收集及故障診斷

1.垃圾收集演算法     1.1 標記-清除演算法           演算法分為標記和清除兩個階段:首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件,標記過程上一篇部落格說過, 後續的幾種演算