java中的GC

概述

在上一章中,咱們瞭解到程序計數器、虛擬機棧、本地方法棧等線程私有的區域的生命週期都是跟隨線程的生命週期的變化而變化的,而java堆和方法區等線程共享的區域的生命週期咱們就沒有介紹了。html

所以,本章的主要內容就是介紹java堆和方法區等線程共享的區域上管理,即java堆和方法區上GC。咱們將從如下幾個方面進行展開:java

  • 什麼樣的對象是垃圾對象(對象回收的原則)
  • 回收的原理
  • 分析與調優的工具

要特別注意,本章節主要討論的是java堆和方法區上的GC。web

什麼樣的對象可以回收

要想了解GC,首先要了解什麼是垃圾對象,或者說是什麼樣的對象才符合垃圾回收的原則?所謂的垃圾就是在jvm中用不到的內存區域,由於內存資源老是有限的,用不到的內存必定要從新被操做系統收集整理後再次使用。因此,垃圾回收的本質就是對內存空間的從新整理,把符合特定條件的內存區域從新回收,以供操做系統後期再次使用。算法

而線程私有的內存空間由線程本身管理,那線程共享的區域的管理就須要額外的功能模塊來管理了,這個額外的功能模塊就是垃圾回收器,又由於線程共享的區域上存儲的主要內容是java的類對象和方法。所以咱們就要研究什麼樣的對象纔可以被回收。瀏覽器

研究對象是否被回收,只須要研究對象是否被使用便可。換句話來講,就是研究對象是否被引用。目前來講,判斷對象是否被引用有兩種方式:安全

引用計數法:爲每個對象都建立一個引用計數的屬性,若是有其餘對象引用這個對象,就爲這個對象的引用計數的屬性加一,引用釋放時計數就減一,計數爲0的時候就能夠被回收了。這種方式沒法解決對象之間相互引用的問題。固然java中也不會使用這種方式來判斷對象是否存活了。服務器

可達性分析法:引入GCroot概念,若是在GCroot和一個對象之間沒有可達路徑,那麼對象就是不可達的。要注意的是:不可達對象不等價於可回收對象,不可達對象變成回收對象至少要通過兩次標記過程,兩次標記以後仍然是可回收對象,那將面臨回收。多線程

在Java語言中,GC Roots包括:併發

  • 虛擬機棧中引用的對象。
  • 方法區中類靜態屬性實體引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI引用的對象。

TODO: 可達性分析法的具體實現過程oracle

因爲java虛擬機規範中,沒有要求虛擬機在方法區實現垃圾收集,所以方法區的垃圾收集是根據具體的虛擬機實現來肯定的。在HotSpot中,方法區就是永久區。永久區的垃圾回收對象是廢棄常量和無用的類。

判斷一個類是否無用比較苛刻,要同時知足如下3個方面的條件:

  • 該類全部的實例都已經回收,也就是 Java 堆中不存在該類的任何實例
  • 加載該類的 ClassLoader 已經被回收
  • 該類對應的 java.lang.Class 對象沒有任何地方唄引用,沒法在任何地方經過反射訪問該類的方法

垃圾回收

垃圾回收由兩部分組成,一部分是GC算法,另外一部分是實現GC算法的回收器。

算法基礎

本小節主要研究垃圾回收算法的種類、具體實現原理、適用對象、優缺點等內容。

  • 標記-整理算法

    • 算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象
    • 它是最基礎的算法,由於後續全部的算法都是在此算法基礎上改進而來的
    • 有兩個缺點:一個是效率問題,標記和清除的效率都不高;另外一個是空間問題,標記清除後悔產生大量不連續的內存碎片,碎片過多或可由於在分配大對象內存空間時找不到足夠連續空間而不得不提早觸發垃圾收集動做
  • 複製算法

    • 它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉
    • 這種算法的代價是要內存空間的浪費,由於有一半空間一直在空閒狀態,此外,對於長生存期的對象來講,複製會致使效率下降
    • IBM研究代表,新生代中98%的對象是「朝生夕死」的,所以也沒有必要按照1:1的比例來劃份內存空間
    • HopSpot中Eden和Servivor的大小比例是8:1,即每次垃圾回收後,新生代可用空間爲總容量的90%(80%+10%),這樣就只有10%的內存空間被浪費了;
    • 固然,還有一個分配擔保,意思就是,若是某次垃圾回收有多餘10%的對象,那多餘的對象能夠由老年代進行擔保,即經過擔保機制,進入老年代
  • 標記-清除算法

    • 根據老年末的特色,使用標記-清除算法,標記清除跟標記整理的第一個步驟同樣,都是標記過程,只不過第二步驟是,將全部存活對象向一端移動,而後清除掉邊界之外的內存
  • 分代回收算法

    • 把堆分爲新生代和老年代,而後根據各個年代的特色採用最適當的收集算法
    • 在新生代中,因爲只有少許對象存活,就選用複製算法;老年代中對象存活率高、沒有額外空間對其分配擔保,就使用標記-清除算法或標記-整理算法

回收器

在早起的jdk版本中,咱們關注的是清理jvm中全部的垃圾,所以產生了單線程的Serial收集器和Serial Old收集器,這保證了清理時的穩定性與高效性,但這樣一樣會帶來一下問題,如因爲清理線程獨佔系統資源形成用戶程序線程暫停而引發全局停頓的問題;後來,咱們開始關注系統的吞吐量(即用戶應用程序運行時間與總運行時間的比值,吞吐量從某種意義上也體現了全局停頓時間的概念),咱們認爲要提升系統的吞吐量,所以產生了Parallel Scavenge收集器和Parallel Old收集器;再後來,咱們把關注點轉移到減小全局停頓時間上,所以產生了跨時代的CMS收集器。

此外,因爲在垃圾收集過程當中,會產生全局停頓的現象,這就引出咱們評判一個垃圾收集器性能好壞的評判標準:咱們在評判一個垃圾收集器性能好壞時,本質上講,咱們是在討論這個垃圾收集器在收集垃圾時所產生的全局停頓時間的長短,即全局停頓時間越短,垃圾收集器的性能越好。可是這句話不是絕對的,咱們只是提供了一種觀察垃圾收集器性能好壞的方法。

下面的內容,咱們是從分代的角度去講述垃圾收集器的種類,固然讀者也能夠從垃圾收集器的發展階段中的關注點來劃分。本小節中也將主要講解各類回收器的種類、適用範圍、實現原理、優缺點、參數控制等內容。

上面的圖很是重要! 說明: 若是兩個收集器之間存在連線說明他們之間能夠搭配使用。

新生代收集器

  • Serial收集器

    • 採用的是複製算法,
    • 只會使用一個CPU或者是一個線程去完成垃圾收集工做,而且在進行垃圾收集的同時,必須暫停其餘全部的工做線程,直至垃圾收集結束
    • 使用-XX:+UseSerialGC參數控制
    • 是Client模式下默認的新生代的垃圾收集器
    • 優勢
      • 單線程的垃圾收集效率較高
      • 穩定性高
      • 多適用到單核
      • 全局停頓時間較少
  • ParNew收集器

    • 是serial收集器的多線程版本
    • 是許多運行在server模式下的虛擬機中首選的新生代收集器
    • 默認開啓和CPU數目相同的線程數,能夠經過-XX:ParallelGCThreads參數來限制垃圾收集器的線程數
  • Parallel Scavenge收集器

    • 採用的是複製算法
    • 它重點關注的是程序要達到一個可控制的吞吐量(即運行代碼時間/(運行代碼時間+垃圾收集時間)),所以也叫作吞吐量收集器
    • 使用-XX:MaxGCPauseMillis參數來控制最大垃圾收集停頓時間
    • 使用-XX:GCTimeRatio參數來直接設置吞吐量大小
    • 使用-XX:+UseAdaptiveSizePolicy參數來開啓GC自適應的調節策略,這個也是與Par New收集器的重要區別

三個新生代收集器所採用的收集算法都是複製算法,而且它們的功能也是逐步完善的。ParNew收集器在Serial收集器的基礎上面加上了多線程的功能,Parallel Scavenge收集器又在ParNew收集器的基礎上加了控制吞吐量的控制功能。

老年代收集器

  • Serial Old收集器

    • 採用標記-整理算法
    • client模式下的默認的老年代垃圾收集器
    • 在server模式下,有兩大用途:
      • 在jdk1.5後搭配Parallel Scavenge收集器使用
      • 做爲CMS收集器的後備預案
  • Parallel Old收集器

    • 採用標記-整理算法
    • 是Parallel Scavenge收集器的老年代版本,言外之意,Parallel Old收集器也一樣具備保證吞吐量的功能
  • :star: CMS收集器

    • 全稱:Concurrent mark sweep(CMS)
    • 使用標記-清除算法
    • 是一種以獲取最短回收停頓時間爲目標的收集器
    • 整個過程分爲四個步驟:
      • 初始標記(initial mark):只是標記GCRoots能直接關聯的對象,要暫停全部的工做線程
      • 併發標記(concurrent mark):進行GCRoots跟蹤的過程,和用戶線程一塊兒工做,不須要暫停工做線程
      • 從新標記(remark):修正因用戶線程繼續運行而致使標記變更的那一部分對象的標記記錄,仍須要暫停全部的工做線程
      • 併發清除(concurrent sweep):清除GCRoots不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程
    • 優勢:併發收集、低停頓
    • 缺點:對CPU資源敏感、沒法收集浮動垃圾、會產生大量空間碎片(能夠經過-XX:+CMSFullGCsBeforeCompaction參數設置執行多少次不壓縮FullGC後,跟着來一次帶壓縮的。如:這個參數的值爲2,就是在執行2次不壓縮的FullGC以後,緊接着會再執行1次壓縮的FullGC)等

:star: 通用收集器

G1收集器(又稱:Garbage—First)是目前技術發展的最前沿成果之一,它既能夠做用到新生代又能夠做用到老年代,所以它也被稱爲通用收集器。

  • 相對於CMS垃圾收集器,它具備如下特色:

    • 基於標記-整理算法,再也不產生內存碎片
    • 能夠很是精確的控制停頓時間,在不犧牲吞吐量的前提下,實現了低停頓垃圾回收
  • 使用範圍

    • 面向服務器
    • 針對配備多顆處理器及大容量內存的機器
  • G1收集器的幾個重要內容

    • G1堆分配

      • G1收集器的java堆的內存佈局與其餘收集器具備很大的差異,它是將整個java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留了新生代和老年代的概念,可是新生代和老年代已經再也不物理隔閡了,他們都是一部分(能夠不連續)Region的集合。它是將整個java堆劃分爲大約2000個大小相等的獨立區域(Region),每一個獨立區域的大小在1Mb到32Mb直接,能夠經過-XX:G1HeapRegionSize參數(該參數的默認值並非必定的,它是根據java堆初始化時大小決定的,取值範圍爲:1M到32M,且要是2的指數)來設置。
      • 圖示:
      • 在上圖中,咱們注意到還有一些Region標明瞭H,它表明Humongous,這表示這些Region存儲的是巨大對象(humongous object,H-obj),即大小大於等於region一半的對象。剩下的區域就是未被分配的空閒區域。
    • 新生代GC

      • 新生代收集跟ParNew相似,當新生代佔用達到必定比例的時候,開始觸發收集。
      • 圖示:
      • 被圈起的綠色部分爲新生代的區域(region),通過Young GC後存活的對象被轉移到一個或者多個區域空閒中,這些被填充的區域將是新的新生代;當新生代對象的年齡(逃逸過一次Young GC年齡增長1)已經達到某個閾值(ParNew默認15),被轉移到老年代的區域中。
      • 回收過程是停頓的(STW,Stop-The-Word);回收完成以後根據Young GC的統計信息調整Eden和Survivor的大小,有助於合理利用內存,提升回收效率。
      • 回收的過程多個回收線程併發收集。
    • 老年代GC

      • 和CMS相似,G1收集器收集老年代對象會有短暫停頓。
      • 包括如下幾個階段:
        • 初始化標記階段:這一階段會捎帶着進行一次新生代的GC,在日誌「GC pause (young)(inital-mark)」部分進行展現。
        • 根區域掃描階段:程序運行過程當中會回收survivor區(存活到老年代),這一過程必須在新生代的GC以前完成。
        • 併發標記階段:在整個堆中進行併發標記(和應用程序併發執行),此過程可能被新生代的GC中斷。在併發標記階段,若發現區域中的全部對象都是垃圾,那個這個區域會被當即回收(圖中打X)。同時,併發標記過程當中,會計算每一個區域的對象活性(區域中存活對象的比例)。
        • 從新標記階段:會有短暫停頓(STW)。再標記階段是用來收集 併發標記階段 產生新的垃圾(併發階段和應用程序一同運行);G1中採用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)
        • 複製或清除階段:多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,併發清空回收區域並把它返回到空閒區域鏈表中。
        • 複製/清除後階段:回收區域的活性對象已經被集中回收到深藍色和深綠色區域。
      • 圖示:

安全點和全局停頓

安全點

GC的停頓主要來源於可達性分析上,程序執行時並不是在全部地方都能停頓下來開始GC,只有在到達安全點時才能暫停。

安全點的選定基本上是以程序「是否具備讓程序長時間執行的特徵」爲標準進行選定的——由於每條指令執行的時間都很是短暫,程序不太可能由於指令流長度太長這個緣由而過長時間運行,「長時間執行」的最明顯特徵就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等,因此具備這些功能的指令纔會產生安全點。

接下來的問題就在於,如何讓程序在須要GC時都跑到安全點上停頓下來,大多數JVM的實現都是採用主動式中斷的思想。

主動式中斷的思想是當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單地設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就本身中斷掛起,輪詢標誌的地方和安全點是重合的,另外再加上建立對象須要分配內存的地方。

全局停頓

// TODO

幾種回收器組合使用的原理圖

  • Serial收集器和SerialOld收集器

  • ParNew收集器與SerialOld收集器

  • Parallel Scavenge收集器和Parallel Old收集器

  • G1收集器

GC日誌分析與調優工具

日誌分析

  • MinorGC日誌格式

  • FullGC日誌格式

調優工具

調優工具分爲兩類,一類是jdk自帶的,一類是第三方的。

  • jps:JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。

  • jstat:JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。

  • jmap:JVM Memory Map命令用於生成heap dump文件

  • jhat:JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看

  • jstack:用於生成java虛擬機當前時刻的線程快照。

  • jinfo:JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數。

  • jconsole:Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控

  • jvisualvm:jdk自帶全能工具,能夠分析內存快照、線程快照;監控內存變化、GC變化等。

  • MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗

  • GChisto:一款專業分析gc日誌的工具

線程、工做內存與主內存

// TODO

參考資料

[0] : Jvm 系列(三):GC 算法 垃圾收集器

[1] : Java虛擬機(JVM)你只要看這一篇就夠了!

[2] : JVM看這一篇就夠了

[3] : JAVA核心知識點整理

[4] : 《深刻理解Java虛擬機——JVM高級特性與最佳實踐》

[5] : Java Hotspot G1 GC的一些關鍵技術

[6] : Getting Started with the G1 Garbage Collector

[7] :JVM GC參數以及GC算法的應用

相關文章
相關標籤/搜索