Java GC

原文:Java GC工做原理以及Minor GC、Major GC、Full GC簡單總結java

名詞解釋:

GC:垃圾收集器算法

Minor GC:新生代GC,指發生在新生代的垃圾收集動做,全部的Minor GC都會觸發全世界的暫停(stop-the-world),中止應用程序的線程,不過這個過程很是短暫。數組

Major GC/Full GC:老年代GC,指發生在老年代的GC。安全

JVM:Java Virtual Machine(Java虛擬機)的縮寫。多線程

正文:

>堆併發

衆所周知,全部經過new建立的對象的內存都在堆中分配,堆被劃分爲新生代和老年代,新生代又被進一步劃分爲Eden和Survivor區,而Survivor由FromSpace和ToSpace組成。jvm

新生代:新建立的對象都是用新生代分配內存,Eden空間不足時,觸發Minor GC,這時會把存活的對象轉移進Survivor區。函數

老年代:老年代用於存放通過屢次Minor GC以後依然存活的對象。佈局

結構圖以下:性能

JVM內存結構之堆  

>棧

每一個線程執行每一個方法的時候都會在棧中申請一個棧幀,每一個棧幀包括局部變量區和操做數棧,用於存放這次方法調用過程當中的臨時變量、參數和中間結果。

>本地方法棧

用於支持native方法的執行,存儲了每一個native方法調用的狀態。

>方法區

存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(PermanetGeneration)來存放方法區。

以上是JVM內存組成結構。

 

進入正題:JVM垃圾回收機制

JVM分別對新生代和老年代採用不一樣的垃圾回收機制。

GC 觸發條件:Eden區滿了觸發Minor GC,這時會把Eden區存活的對象複製到Survivor區,當對象在Survivor區熬過必定次數的Minor GC以後,就會晉升到老年代(固然並非全部的對象都是這樣晉升的到老年代的),當老年代滿了,就會報OutofMemory異常。

新生代的GC(Minor GC):

新生代一般存活時間較短基於Copying算法進行回收,所謂Copying算法就是掃描出存活的對象,並複製到一塊新的徹底未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace 之間copy。新生代採用空閒指針的方式來控制GC觸發,指針保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從Eden到Survivor,最後到老年代。

在執行機制上JVM提供了串行GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew):

串行GC

在整個掃描和複製過程採用單線程的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是很是高的應用上,是client級別默認的GC方式,能夠經過-XX:+UseSerialGC來強制指定。

並行回收GC

在整個掃描和複製過程採用多線程的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別默認採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數。

並行GC

與老年代的併發GC配合使用。

老年代的GC(Major GC/Full GC):

老 年代與新生代不一樣,老年代對象存活的時間比較長、比較穩定,所以採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,而後再進行回收未被 標記的對象,回收後對用空出的空間要麼進行合併、要麼標記出來便於下次進行分配,總之目的就是要減小內存碎片帶來的效率損耗。

在執行機制上JVM提供了串行 GC(Serial MSC)、並行GC(Parallel MSC)和併發GC(CMS)。

串行GC(Serial MSC)

client模式下的默認GC方式,可經過-XX:+UseSerialGC強制指定。每次進行所有回收,進行Compact,很是耗費時間。

並行GC(Parallel MSC)(吞吐量大,可是GC的時候響應很慢)

server模式下的默認GC方式,也可用-XX:+UseParallelGC=強制指定。能夠在選項後加等號來制定並行的線程數。

併發GC(CMS)(響應比並行gc快不少,可是犧牲了必定的吞吐量)

使 用CMS是爲了減小GC執行時的停頓時間,垃圾回收線程和應用線程同時執行,可使用-XX:+UseConcMarkSweepGC=指定使用,後邊接 等號指定併發線程數。CMS每次回收只停頓很短的時間,分別在開始的時候(Initial Marking),和中間(Final Marking)的時候,第二次時間略長。CMS一個比較大的問題是碎片和浮動垃圾問題(Floating Gabage)。碎片是因爲CMS默認不對內存進行Compact所致,能夠經過 -XX:+UseCMSCompactAtFullCollection。

虛擬機給每一個對象定義了一個對象年齡(Age)計數器。若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor區,並將對象年齡設爲 1。對象在Survivor區中每熬過一次Minor GC,年齡就增長1,當它的年齡增長到必定程度(默認爲15)時,就會被晉升到老年代中。對象晉升老年代的年齡閾值,能夠經過參數 -XX:MaxTenuringThreshold 來設置。

GC判斷對象是否"存活"或"死去"(GC回收的對象)

1.引用計數器算法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器的值加1;當引用失效時,計數器的值減;當該對象的計數器的值爲0時,標誌該對象失效。

2.跟搜索算法

基本思路:經過一系列的名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講就是從GC Roots到這個對象不可達)時,則證實對象是不可用的。

附JVM GC組合方式:

 

原文:Java虛擬機精講以內存分配與垃圾回收

 

1 線程共享內存區

Java堆區

  • 用於存儲Java對象實例,可是不必定是Java對象內存分配的惟一選擇(爲了下降GC頻率).在JVM啓動的時候大小就已經設定好了.(-Xmx最大 -Xms起始) 超過最大內存的時候,拋出OOM異常.
  • 實際的內存空間能夠不連續,是GC的重點區域.
  • YoungGen新生代(Eden, From Survivor, To Survivor) ; OldGen老年代(OldGen).根據不一樣的代選擇不一樣的GC算法.

方法區

  • 存儲了每個Java類的結構信息,好比 運行時常量池 , 字段和方法數據 , 構造函數 , 普通方法的字節碼內容 以及 類,實例,接口初始化時要用到的特殊方法等數據.
  • jvm規範沒有對方法區的實現有明確要求,在HotSpot中,它屬於Java堆區的一部分
  • 在JVM啓動的時候建立,也能夠不連續,有時候被稱爲永久代,它不會頻繁的GC,可是不表明它裏面數據永遠不會被回收.若是沒有顯示要求不對方法區進行內存回收的狀況下,GC的回收目標僅針對方法區的常量池類型卸載
  • 超過-XX:Max-PermSize也會OOM

運行時常量池

  • 屬於方法區的一部分.運行時常量池就是字節碼文件中常量池表的運行時表示形式.
  • 當類加載器成功的將一個類或者接口裝載進JVM後,就會建立與之對應的運行時常量池.

2 線程私有內存區

PC寄存器

  • 無OOM
  • 爲何每一個線程都要?答:CPU作任務切換,每一個線程都要記錄正在執行的當前字節碼指令地址,從而每一個線程均可以獨立計算.
  • JVM的解釋器須要經過改變PC寄存器的值來明確下一條有關執行什麼樣的字節碼指令

    Java棧(Java虛擬機棧)

  • Java棧用於存儲棧幀,生命週期和線程的生命週期一致
  • 對應Java堆中存儲對象,Java棧中局部變量表用於存儲各種原始數據類型,對象引用,以及returnAdress類型.

3 自動內存管理

內存分配原理

  • jvm包含三種引用了類型:類類型,數組類型,接口類型,分別對應建立的值是 類實例, 數組實例, 以及實現了某個接口的派生類實例.
  • 下圖是jvm中具體的建立對象實例 

  • 分配內存有指針碰撞(已用的未用的分兩邊,經過修改中間指針的偏移量) 和 空閒列表(Free List)

堆區和方法區

  • 堆區和方法區是線程共享,在併發環境下從堆區中劃份內存空間是非線程安全的.因此一個類若是在分配內存以前已經成功完成類裝載步驟以後,JVM會優先選擇在TLAB(本地線程分配緩衝區,是堆區中一塊線程私有的區域,包含在Eden空間內,缺省很小,佔Eden的1%,能夠經過參數調整),能夠避免一系列的非線程安全問題,還能提高內存分配的吞吐量.這種內存分配方式叫作快速分配策略.(-XX:UseTLAB)
  • 一旦對象在TLAB空間分配內存失敗,JVM會嘗試經過加鎖來確保原子性,從而直接在Eden分配內存.
  • 分配好內存空間後,接下來就是初始化對象實例,首先對分配的內存空間進行零值初始化,再接下來是初始化對象頭和實例數據.到這裏,一個Java對象實例纔是真正的建立成功.

逃逸分析與棧上分配

  • 一個對象被定義在方法體內部以後,一旦其應用被外部成員引用,這個對象就發生了逃逸,反之,JVM就會爲其在棧幀中分配內存空間(這是優化技術)
public class StackAllocation {
  public StackAllocation obj;

  //逃逸
  public StackAllocation getStackAllocation() {
    return null == obj ? new StackAllocation() : obj;
  }

  //爲成員變量賦值,逃逸
  public void setStackAllocation() {
    obj = new StackAllocation();
  }

  //引用成員變量的值,逃逸
  public void useStackAllocation1() {
    StackAllocation obj = getStackAllocation();
  }

  //對象的做用於僅在方法體內,未逃逸
  public void useStackAllocation2() {
    StackAllocation obj = new StackAllocation;
  }
}

對象內存佈局與OOP-Klass模型

GC算法和垃圾收集器

  • GC(Garbage Collector 垃圾收集器),其實內存劃分(新生代,老年代)是徹底依賴於GC的設計.當內存空間中的內存消耗達到必定的閾值以後,GC會進行垃圾回收.
  • 能夠根據如下6點評估一款GC的性能
  1. 吞吐量:程序的運行時間/(程序的運行時間 + 內存回收的時間)
  2. 垃圾收集開銷
  3. 暫停時間
  4. 收集頻率
  5. 堆空間:java堆區鎖佔用的內存大小
  6. 快速:一個對象從誕生到被垃圾回收所經歷的時間.

垃圾標記: 根搜索算法

  • 通常不用引用計數法.沒法解決死亡對象的互相引用致使沒法垃圾回收,因此HotSpot使用根搜索算法,只有可以被根對象集合直接或者間接鏈接的對象纔是存活對象.當目標對象不可達的時候,即可以在instanceOopDesc的MarkWord中將其標記爲垃圾對象.
  • 根對象集合:
  1. Java棧中的對象引用
  2. 本地方法棧中的對象引用
  3. 運行時常量池中的對象引用
  4. 方法區中類靜態屬性的對象引用(因此這個要注意 靜態變量的定義!)
  5. 與一個類對應的惟一數據類型的Class對象

垃圾回收:分代收集算法

  • 上一步成功的區分出了存活和死亡對象.接下來就是垃圾回收算法.

    標記-清除算法(Mark-Sweep)

  • 把垃圾回收任務分爲兩個階段,分別是垃圾回收和內存釋放.簡單,可是效率低下,並且會產生內存碎片

    複製算法(Copying)

  • 就是由於這個算法,因此內存是分代的!
  • java的大多數對象都是瞬時對象,生命週期很是短.複製算法普遍用於新生代中.新生代分爲Eden,From Suvivor和To Suvivor,Eden和另外兩個Survior空間缺省所佔比例爲8:1,能夠經過-XX:SurvivorRatio調整.
  • 當執行一次Minor GC,Eden中存活的對象會被複制到To Suvivor空間內,而且以前經歷過一次Minor GC並在From Suvivor存活下來的對象若是還年輕的話會被複制到To Suvivor.

  • 知足兩種特殊狀況,Eden和From空間的存活對象不會被複制到To空間.

  1. 存活的對象的分代年齡超過-XX:MaxTenuringThreshold所指的的閾值時,會直接晉升到老年代中.
  2. 當To空間的容量達到閾值的時候,存活的對象也會直接晉升到老年代中
  • 當執行完Minor GC以後,Eden空間和From空間將會被清空,存活下來的對象會所有存在To空間內,接下來From空間和To空間將會互換位置(無非就是使用To空間做爲一個臨時的空間交換角色,因此務必保證一個survivor空間是空的)
  • 不適用與老年代中的內存回收,由於老年代中的對象的生命週期都比較長,因此會有不少的複製,效率和效果都不太好

標記-壓縮算法(Mark-Compact)

  • 適用於老年代
  • 當標記出垃圾對象以後,會將全部的存活對象移動到一個規整且連續的空間,而後執行Full GC(Major GC),垃圾回收以後,已用和未用的內存都各自一邊.

總結

  • 新生代的GC算法一速度優先,新生代的對象生命週期都很是短暫,內存空間也比較小.因此這塊的垃圾回收會很是頻繁.
  • 老年代的GC算法使用更節省內存的算法,老年代的對象生命週期都比較長,而且老年代佔了絕大部分的堆空間.

垃圾收集器

  • 上面說的是JVM的內存回收算法,接下來講的是GC的具體實現.有許多的GC版本,好比:Serial/Serial Old,Parallel/Parallel Old,CMS,G1
  • 這些新生代和老年代的GC算法能夠自由組合 

  • 兩個很是重要的概念:串行仍是並行回收,併發仍是"Stop-the-World"機制

  • 串行回收簡單來講舊書多個CPU能夠用時,也只有一個CPU用於垃圾回收操做,而且在執行垃圾回收的時候,程序中的工做線程會被暫停(注意這個的區別),串行回收缺省在Client模式下的JVM.並行收集能夠運用多個CPU同時進行垃圾收集,提高了吞吐量,可是仍是要"STW",這個必定要注意.
  • 第二個概念是說的是回收的時候,要不要stw的,最新的G1也作不到徹底不須要STW.

串行回收:Serial收集器

  • 用於新生代,採用複製算法,串行回收和STW.是Client下的缺省新生代GC
  • Serial Old 採用標記-壓縮算法,其餘同樣.
  • -XX:+UseSerialGC

並行回收:ParNew收集器

  • 用並行方式執行內存回收,其餘和Seial幾乎沒有區別.在單個cpu下,ParNew不見得會比Serial高效
  • 在某些注重低延遲的應用場景中,ParNew+CMS(Concurrent-Mark-Sweep)收集器組合執行Server模式下的內存回收幾乎是最佳的選擇.
  • -XX:+UseParNewGC

程序吞吐量優先:Parallel收集器

  • 複製算法,並行回收和STW,
  • 和ParNew最大的不通是Parallel能夠控制程序的吞吐量大小.
  • -XX:GCTimeRatio 設置內存回收的時間所佔JVM運行總時間的比例(也就是控制GC的執行頻率,公式爲1/(1+N),默認值是99,也就是說將只有1%的時間用於執行垃圾回收)
  • -XX:MaxGCPauseMillis設置STW的暫停時間閾值,若是指定了該選項,Parallel收集器將會盡量地在指定時間範圍完成內存回收.
  • 注意!吞吐量和低延遲這兩個目標實際上是存在相互競爭的矛盾,若是選擇吞吐量優先,就會下降內存回收的執行頻率,這會致使GC須要更長的暫停時間來執行垃圾回收.若是選擇以低延遲優先,那麼爲了下降每次GC的時間,只能更加頻繁的進行GC,這會致使吞吐量降低.
  • -XX:UseAdaptiveSizePolicy 用於甚至GC的自動分代大小調整策略,這個JVM就是自動調整相關參數
  • Parallel Old採用標記-壓縮算法,Parallel+parallel Old是個不錯的選擇

低延遲:CMS(Concurrent-Mark-Sweep)

  • 是優秀的老年代收集器,採用標記-清除算法,也會stw
  • 有如下階段:
  1. 初始標記階段
  2. 併發標記階段
  3. 再次標記階段,由於工做線程和垃圾回收線程共同工做,因此須要再次標記.因此不可避免的仍是會有漏網之魚
  4. 併發清除階段
  • 因爲採用標記-清除(通常老年代採用標記壓縮),因此不可避免的會有內存碎片,因此使用CMS爲新對象分配內存空間的時候,將沒法使用指針碰撞技術,只能選擇空閒列表進行內存分配
  • -XX:+UseCMSCompactAtFullCollection,用於在執行完指定的FullGC以後是否對內存空間進行壓縮整理.
  • -XX:CMSFullGCsBeforeCompaction 用於設置執行多少次Full GC以後對內存空間進行壓縮整理
  • 一個要糾正的點是 Full-GC是遍及整個堆空間的,不僅是在老年代中.而CMS能夠提供-XX:CMSInitiatingOccupancyFraction用於設置當老年代的內存使用率達到多少的時候執行內存回收.(缺省92%) 注意 這裏的內存回收僅限於老年代,因此能夠有效的下降FullGC的次數

區域分代式:G1(Garbage-First)收集器

相關文章
相關標籤/搜索