1. 程式人生 > >Java虛擬機器之垃圾收集器

Java虛擬機器之垃圾收集器

一、物件引用

JDK1.2之前,Java中的引用定義很很純粹:如果reference型別的資料中儲存的數值代表的是另外一塊記憶體的起始地址,就稱這塊記憶體代表著一個引用。但在JDK1.2之後,Java對引用的概念進行了擴充,將其分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)四種,引用強度依次減弱。
(1)強引用:如“Object obj = new Object()”,這類引用是Java程式中最普遍的。只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的物件。
(2)軟引用:它用來描述一些可能還有用,但並非必須的物件。在系統記憶體不夠用時,這類引用關聯的物件將被垃圾收集器回收。JDK1.2之後提供了SoftReference類來實現軟引用。
(3)弱引用:它也是用來描述非需物件的,但它的強度比軟引用更弱些,被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。在JDK1.2之後,提供了WeakReference類來實現弱引用。
(4)虛引用:最弱的一種引用關係,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的是希望能在這個物件被收集器回收時收到一個系統通知。JDK1.2之後提供了PhantomReference類來實現虛引用。

二、垃圾物件的判定

Java堆中存放著幾乎所有的物件例項,垃圾收集器對堆中的物件進行回收前,要先確定這些物件是否還有用,判定物件是否為垃圾物件有如下演算法:
(1)引用計數演算法
給物件新增一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,任何時刻計數器都為0的物件就是不可能再被使用的。
引用計數演算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的選擇,當Java語言並沒有選擇這種演算法來進行垃圾回收,主要原因是它很難解決物件之間的相互迴圈引用問題。
(2)可達性分析演算法
Java和C#中都是採用可達性分析演算法來判定物件是否存活的。這種演算法的基本思路是通過一系列名為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,就證明此物件是不可用的。如圖所示,object5、object6和object7雖然互相有關聯,但它們到GC Roots是不可達的,所以會被判定為可回收的物件。
可達性分析演算法判斷物件是否可回收

在Java語言裡,可作為GC Roots的物件包括下面幾種:
1.虛擬機器棧(棧幀中的本地變量表)中引用的物件。
2.方法區中的類靜態屬性引用的物件。
3.方法區中的常量引用的物件。
4.本地方法棧中JNI(Native方法)的引用物件。

實際上,在可達性分析演算法中,要真正宣告一個物件死亡,至少要經歷兩次標記過程:如果物件在進行根搜尋後發現沒有與GC Roots相連線的引用鏈,那它會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋finalize()方法,或finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況都視為沒有必要執行。如果該物件被判定為有必要執行finalize()方法,那麼這個物件將會被放置在一個名為F-Queue佇列中,並在稍後由一條由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行finalize()方法。finalize()方法是物件逃脫死亡命運的最後一次機會(因為一個物件的finalize()方法最多隻會被系統自動呼叫一次),稍後GC將對F-Queue中的物件進行第二次小規模的標記,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中讓該物件重引用鏈上的任何一個物件建立關聯即可。而如果物件這時還沒有關聯到任何鏈上的引用,那它就會被回收掉。

三、垃圾收集演算法

判定除了垃圾物件之後,便可以進行垃圾回收了。下面介紹一些垃圾收集演算法
(1)標記清除演算法
標記—清除演算法是最基礎的收集演算法,它分為“標記”和“清除”兩個階段:首先標記出所需回收的物件,在標記完成後統一回收掉所有被標記的物件,它的標記過程其實就是前面的根搜尋演算法中判定垃圾物件的標記過程。標記—清除演算法的執行情況如下圖所示:
標記-清除演算法
該演算法的缺點:
1.標記和清除過程的效率都不高。
2. 標記清除後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致,當程式在以後的執行過程中需要分配較大物件時無法找到足夠的連續記憶體而不得不觸發另一次垃圾收集動作。

(2)複製演算法
複製演算法是針對標記—清除演算法的缺點,在其基礎上進行改進而得到的,它講課用記憶體按容量分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊記憶體上面,然後再把已使用過的記憶體空間一次清理掉。
複製演算法的執行情況如下圖所示:
複製演算法
複製演算法的優點:
1. 每次只對一塊記憶體進行回收,執行高效。
2.只需移動棧頂指標,按順序分配記憶體即可,實現簡單。
3. 記憶體回收時不用考慮記憶體碎片的出現。
複製演算法的缺點:
可一次性分配的最大記憶體縮小了一半。

(3)標記—整理演算法
複製演算法比較適合於新生代,在老年代中,物件存活率比較高,如果執行較多的複製操作,效率將會變低,所以老年代一般會選用其他演算法,如標記—整理演算法。該演算法標記的過程與標記—清除演算法中的標記過程一樣,但對標記後出的垃圾物件的處理情況有所不同,它不是直接對可回收物件進行清理,而是讓所有的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。
標記—整理演算法的回收情況如下所示:
標記整理演算法

(4)分代收集演算法
當前商業虛擬機器的垃圾收集 都採用分代收集,它根據物件的存活週期的不同將記憶體劃分為幾塊,一般是把Java堆分為新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量物件死去,只有少量存活,因此可選用複製演算法來完成收集,即Minor GC,而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除演算法或標記—整理演算法來進行回收,即Full GC。

四、垃圾收集器

基於JDK1.7Update 14之後的HotSpot虛擬機器包含的所有收集器如圖所示
垃圾收集器
圖中展示了7種作用於不同分代的收集器,如果二個收集器之間存在連線,表示它們可以搭配使用。

Java虛擬機器規範中對垃圾收集器應該如何實現並沒有任何規定,實際上到目前為止還沒有最好的收集器出現,更加沒有萬能的收集器,所以我們選擇的只是對具體應用最合適的收集器。
Serial 收集器
它是最基本,發展歷史最悠久的收集器,曾經是虛擬機器新生代收集的唯一選擇。特點就是,它是一個“單執行緒”的收集器,所謂“單執行緒”是指它不僅僅只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是,在它進行垃圾收集時,必須暫停其他所有的工作執行緒,直到它收集結束。這顯然是很讓人難以接受的,但是現代虛擬機器不斷優化,不斷縮減停頓時間,將停頓時間完全可以控制在幾十毫秒最多一百多毫秒以內,那麼只要不是頻繁發生,這點停頓完全可以接受。針對執行在Client模式下的虛擬機器來說,例如使用者的桌面應用場景,分配給虛擬機器管理的記憶體一般來說不會太大,收集幾十兆甚至一兩百兆的新生代,Serial收集器完全夠用。

parNew收集器
parNew是Serial的多執行緒版本,其核心就是就是運用多個執行緒進行垃圾收集。但是在單CPU的環境下絕對不會比Serial收集器有更好的效果,甚至由於存線上程互動的開銷,該收集器在通過超執行緒技術實現的兩個CPU的環境中都不可能百分之百地保證可以超越Serial收集器。但當可用CPU數量增多時,能夠更加高效的利用系統資源。
parNew收集器除了多執行緒收集之外,其他與Serial收集器相比並沒有太多創新之處,但它卻是許多執行在Server模式下的虛擬機器中首選的新生代收集器,其中一個原因就是,除了Serial收集器外,目前只有它能與CMS收集器配合工作。

Parallel Scavenge收集器
Parallel Scavenge收集器特別之處在於,其他收集器的關注點都是儘可能地縮短垃圾收集時使用者執行緒的停頓時間,Parallel Scavenge收集器的目標則是達到一個控制的吞吐量。吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間),假設虛擬機器執行100分鐘,垃圾收集花費1分鐘,那麼吞吐量就是99%。Parallel Scavenge收集器提供了很多可精確設定的細節引數,可以精確控制最大垃圾收集停頓時間還有吞吐量的大小,特別的,當自適應引數開啟後,就不需要手動去指定新生代的大小等細節引數,虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數以提供最合適的停頓時間或或者最大的吞吐量,這種方式就是所謂的GC 自適應調節策略。

Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,同樣是單執行緒的,使用“標記-整理演算法”。這個收集器主要意義也是在於給Client模式下的虛擬機器使用。

Parallel Old收集器
Parallel Old收集器是 Parallel Scavenge的老年代版本,使用多執行緒和“標記-整理”演算法。JDK 1.6 之後才開始提供了這個收集器。在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old 收集器。

CMS收集器
CMS(Concurrent Mark Sweep)收集器是基於“標記-清除”演算法實現的,它使用多執行緒的演算法去掃描堆(標記)並對發現的未使用的物件進行回收(清除)。整個過程分為4個步驟:
1.初始標記
2.併發標記
3.重新標記
4.併發清除
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正併發標記期間,因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短。

CMS收集器的缺點:
1.吞吐量低的它使用更多的 CPU,為了使應用程式提供更好的體驗,通過使用多個執行緒來執行掃描和收集。
2.無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS併發清理階段使用者執行緒還在執行著,伴隨程式的執行自然還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理掉它們,只好留待下一次GC時再將其清理掉。這一部分垃圾就稱為“浮動垃圾”。
3.基於標記-清除演算法的它收集結束會產生大量碎片。

G1收集器
與其他收集器相比,G1收集器具備如下特點。
1.並行與併發
G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU來縮短Stop-The-World停頓的時間,部分其他收集器原本需要停頓Java執行緒執行的GC動作,G1收集器仍然可以通過併發的方式讓Java程式繼續執行。
2.分代收集
與其他收集器一樣,分代概念在G1中依然得以保留。
3.空間整合
G1收集器是基於“標記-整理”演算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間執行的應用系統來說非常重要。
4.可預測的停頓
它可以非常精確地控制停頓,既能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,具備了一些實時Java(RTSJ)的垃圾收集器的特徵。

如果不計算維護Remembered Set的操作,G1收集器的運作大致分為4個步驟:
1.初始標記
2.併發標記
3.最終標記
4.篩選回收
初始標記僅僅只是標記一下GC Roots能直接關聯到的物件並修改TAMS(Next Top at Mark Start)的值。併發標記是從GC Root開始對堆中物件進行可達性分析。最終標記是為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄。最後在篩選回收階段對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來制定回收計劃。

【參考資料】
《深入理解JVM虛擬機器》周志明 著

相關推薦

Java虛擬機器垃圾收集

一、物件引用 JDK1.2之前,Java中的引用定義很很純粹:如果reference型別的資料中儲存的數值代表的是另外一塊記憶體的起始地址,就稱這塊記憶體代表著一個引用。但在JDK1.2之後,Java對引用的概念進行了擴充,將其分為強引用(Strong Ref

深入理解java虛擬機器垃圾收集

  前言   如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。java虛擬機器規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠商、不同的版本的虛擬機器所提供的垃圾收集器都有可能會有很大的區別,並且一般都會提供引數供使用者根據自己的應用特點和要求組合出各個年代所使用的收集

Java虛擬機器垃圾收集和記憶體分配策略

垃圾收集器和記憶體分配策略 垃圾收集器 Serial 收集器 ParNew 收集器 Parallel Scavenge 收集器 Serial Old 收集器 Parallel Old 收集器 CMS 收集器(C

Java虛擬機器垃圾收集有哪些?

Serial收集器 Serial收集器是新生代的垃圾收集器,是一個單執行緒的收集器,它開始工作時會暫停掉其它所有的工作執行緒,一直到它工作結束。它是虛擬機器執行在Client模式下的預設新生代垃圾收集器,採用複製演算法。 ParNew收集器 ParNe

《深入理解Java虛擬機》——垃圾收集與內存分配策略

特點 兩個 instance 統一 tro 過程 引用計數 分析算法 效率問題 GC需要完成: 哪些內存需要回收 什麽時候回收 如何回收 如何確定對象不再使用 引用計數算法 給對象添加一個引用計數器,當有一個地方引用它時,計數器值進行加1操作;當引用失效時,計數器值

深入理解Java虛擬垃圾收集

native 直觀 軟引用 老年 系統清理 邊界 lan 除了 每次 “生存還是死亡” 如何來判定對象是否存活?針對這個問題書中給出了兩種算法,分別是引用計數算法和可達性分析算法 引用計數算法 該算法的思路簡單並且易於實現。我們給對象中添加一個引用計數器,當有一個地方引用

深入JAVA虛擬垃圾收集

收集 ESS 解釋 美的 路徑 平靜的 過程 對象創建 image 前言: 說起垃圾收集器,JAVA開發者肯定是聽得耳朵都起繭子了。如果讓你設計一個JAVA垃圾收集器,那麽你關註那些點呢? // 1.哪些內存需要回收? // 2.什麽時候回收? // 3.如何回收? 這篇

java虛擬機器4.垃圾收集演算法

 java記憶體執行時的各個部分,其中程式計數器、虛擬機器棧、本地方法棧3個區域隨執行緒而生,隨執行緒而滅;棧中的棧幀隨著方法的進入和退出而有條不絮地執行著出棧和入棧操作。每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知的(儘管JIT編譯器會進行一些優化,但大體可認為是編譯期可預知的),因

java虛擬機器5.垃圾收集演算法

 1. 標記 - 清除演算法 首先標記出所有需要回收的物件,在標記完成後再統一回收。它的標記過程其實基於上面的可達性分析演算法。之所以說這是最基礎的收集演算法,是因為後續的收集演算法都是基於這種思路並對其不足進行改進而得到的。它的不足有兩個: 標記和清除過程效率不高; 標記清

Java虛擬機器垃圾收集演算法

垃圾收集演算法 垃圾收集演算法 標記 - 清除演算法 複製演算法 標記 - 整理演算法 分代收集演算法 參考 垃圾收集演算法 標記 - 清除演算法 演算法分為兩個階段:標記和清除。首先標

閱讀筆記-深入理解java虛擬機器-1-垃圾回收

垃圾蒐集器可以混用 垃圾收集其是記憶體回收的具體實現。收集演算法是記憶體回收的方法論 Serial收集器: 基本,最久的回收器,並不僅僅是使用一個CPU或者一條收集執行緒完成垃圾收集工作,重要的是在垃圾回收時必須暫停其他所有的工作執行緒(stop the world)

《深入理解Java虛擬機器》學習筆記垃圾收集與記憶體分配策略

一、概述 GC(Garbage Collection)需要完成的三件事 (1)哪些記憶體需要回收 (2)什麼時候回收 (3)如何回收 GC主要面向Java堆和方法區中的記憶體 原因:這部份

深入理解Java虛擬機器——JVM垃圾回收機制和垃圾收集詳解

一:概述 說起垃圾回收(Garbage Collection,GC),很多人就會自然而然地把它和Java聯絡起來。在Java中,程式設計師不需要去關心記憶體動態分配和垃圾回收的問題,顧名思義,垃圾回收就是釋放垃圾佔用的空間,這一切都交給了JVM來處理。本文主要解答三個

java基礎垃圾收集

文章目錄 概述 上一篇主要說了GC的過程,這裡總結一下java的幾種收集器和演算法 前置結論 儘可能將物件分配到新生代,因為full GC成本高於minor GC 儘量少使用大物件 JIT編譯引數 發生oom時執行指令碼 -XX:OnoomError=D:\r

Java程式設計師從笨鳥到菜鳥(九十五)深入java虛擬機器(四)——java虛擬機器垃圾回收機制

         Java語言從出現到現在,一直佔據程式語言前列,他很大的一個原因就是由於java應用程式所執行的平臺有關。我們大家都知道java應用程式執行在java虛擬機器上。這樣就大大減少了java應用程式和底層作業系統打交道的頻率。這也就為java程式的跨平臺提供了良好的基礎。在java虛擬機器中

想買保時捷的運維李先生學Java效能 垃圾收集

前言 垃圾收集演算法是記憶體回收的方法論;垃圾收集器是記憶體回收的具體實現。Java虛擬機器規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠商、不同版本的虛擬機器所提供的垃圾收集器都有很大的差別,並且一般都會提供引數供使用者根據自己的應用特點和要求組合出各個年代所使用的收集器。   虛擬機

jvm系列(三):java GC算法 垃圾收集

應對 sca 互聯 都是 生命 改進 壓縮 速度 垃圾收集器 原文鏈接:http://www.cnblogs.com/ityouknow/p/5614961.html 概述 垃圾收集 Garbage Collection 通常被稱為“GC”,它誕生於1960年 MIT 的

Java虛擬垃圾回收算法思想總結

收集 內存 弊端 內存空間 碎片 加減 正在 分區 java 1、引用計數法   這是個比較古老而經典的垃圾回收算法,其核心就是在對象被其他所引用的時候計數器加1,而當引用失去時減1。這個方法有非常嚴重的問題:無法此話有理循環引用的情況,還有就是每次進行加減操作比較浪費系統

Java虛擬機器‘靜態分派、動態分派’

Java是一門面向物件的語言,因為Java具備面向物件的三個特性:封裝、繼承、多型。分派的過程會揭示多型特性的一些最基本的體現,如“過載”和“重寫”在Java虛擬機器中是如何實現的,並不是語法上如何寫,我們關心的依然是虛擬機器如何確定正確的目標方法。 一、靜態分派 先看一段程式碼 pac

《深入理解JAVA虛擬機器垃圾回收時為什麼會停頓

停頓現象 很多網上資料都會說到JAVA語言的一個劣勢就是垃圾蒐集時,整個程序會停頓。 到底是不是呢? 答案是確實存在。   為什麼會停頓 垃圾收集的一個前提是要判斷程序中的物件哪些是垃圾記憶體,哪些不是。 怎麼判斷呢,JVM裡面使用了一種叫可達性分析的技術來列舉根節點。 一言以蔽之,