JVM內存回收機制

JVM內存回收機制
標籤: JVM GC 垃圾回收 內存管理html

0.說明

當JVM建立對象遇到內存不足的時候,JVM會自動觸發垃圾回收garbage collecting(簡稱GC)操做,將再也不使用但仍存在JVM內存中的對象當作垃圾同樣直接清理掉,釋放被佔用的內存空間,供新建立的對象使用。
那麼問題來了,要讓系統可以自動實現不被引用對象的回收,有幾個問題須要解決:java

  • Who:哪些是再也不使用要被當作「垃圾」回收處理的對象?也就是要肯定垃圾對象。
  • Where:在哪裏執行垃圾回收?明確要清理的內存區域。
  • When:何時執行GC操做?即JVM觸發GC的時機。
  • How:怎麼樣進行垃圾對象處理?即GC的實現算法。

關於第二點,對於oracle Hotspot VM的 GC操做主要回收的內存區域是JVM中的堆(分爲年輕代和年老代,年輕代又分Eden和兩個survivor區域),JVM內存結構不是本文的重點,咱們在看本文前要對JVM內存結構有必定的瞭解,這裏不做詳細分析。算法


1.判斷對象是否能夠被回收(判別算法或搜索算法)

下面的算法回答了who的問題。數據庫

1.1 引用計數法

每一個對象建立的時候,會分配一個引用計數器,當這個對象被引用的時候計數器就加1,當不被引用或者引用失效的時候計數器就會減1。任什麼時候候,對象的引用計數器值爲0就說明這個對象不被使用了,就認爲是「垃圾」,能夠被GC處理掉。數組

評價緩存

  • 【優勢】算法實現簡單。
  • 【缺點】不能解決對象之間循環引用的問題。有垃圾對象不能被正確識別,這對垃圾回收來講是很致命的,因此GC並無使用這種搜索算法。

1.2 根搜索算法

以一些特定的對象做爲基礎原始對象,或者稱做「根」,不斷往下搜索,到達某一個對象的路徑稱爲引用鏈。
若是一個對象和根對象之間有引用鏈,即根對象到這個對象是可到達的,則這個對象是活着的,不是垃圾,還不能回收。例如,假設有根對象O,O引用了A對象,同時A對象引用了B對象,B對象又引用了C對象,那麼對象C和根對象O之間的路徑的可達的,C對象就不能當作垃圾對象。引用鏈爲O->A->B->C。
反之,若是一個對象和根對象之間沒有引用鏈,根對象到這個對象的路徑是不可達的,那麼這個對象就是可回收的垃圾對象。多線程

評價併發

  • 【優勢】可找到因此得垃圾對象,而且完美解決對象之間循環引用的問題。
  • 【缺點】不可避免地要遍歷全局全部對象,致使搜索效率不高。

根搜索算法是如今GC使用的搜索算法。oracle

能夠當作GC roots的對象有如下幾種:jvm

  • 虛擬機棧中的引用的對象。(java棧的棧幀本地變量表)

  • 方法區中的類靜態屬性引用的對象。

  • 方法區中的常量引用的對象。(聲明爲final的常量對象)

  • 本地方法棧中JNI的引用的對象。(本地方法棧的棧幀本地變量表)

    下面是從網上找來的圖,將就看看:GC ROOTS就是跟對象節點,藍色的是可達的引用鏈,引用鏈上的對象是活着的,不能被當作垃圾對象回收。相反暗灰色的路徑表示不可達的路徑,這些對象將會被回收。每一個圈圈裏面的數字,表示其被引用的次數,沒錯,就是上面說到的引用計數法的計數值。跟搜索算法示例圖


2.GC算法

這裏討論的是oracle的Hotspot VM常見的垃圾回收算法。使用的搜索算法都是基於根搜索算法實現的。

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

該算法分兩步執行:

1) 標記Mark:從GC ROOTS開始,遍歷堆內存區域的全部根對象,對在引用鏈上的對象都進行標記。這樣下來,若是是存活的對象就會被作了標記,反之若是是垃圾對象,則沒作有標記。GC很容易根據有沒有被作標記就完成了垃圾對象回收。

2) 清除Sweep:遍歷堆中的全部的對象(標記階段遍歷的是全部根節點),找到未被標記的對象,直接回收所佔的內存,釋放空間。

評價

  • 【優勢】沒有產生額外的內存空間消耗,內存利用率高。
  • 【缺點】效率低,清除階段要遍歷全部的對象;回收的垃圾對象是在各個角落的,直接回收垃圾對象,致使存在不連續的內存空間,產生內存碎片。

標記-清除算法操做的對象是【垃圾對象】,對於活着的對象(被標記的對象),它則直接不理睬。

2.2 複製算法(Copying)

複製算法把內存區間一分爲二,有對象存在的一半區間稱爲「活動區間」,沒有對象存在處於空閒狀態的空間則爲「空閒區間」。
當內存空間不足時觸發GC,先採用根搜索算法標記對象,而後把活着的對象所有複製到另外一半空閒區間上,複製算法的「複製」就來自這一操做。複製到另外一半區間的時候,嚴格按照內存地址依次排列要存放的對象,而後一次性回收垃圾對象。
這樣原來的空閒區間在GC後就變成活動區間,並且內存順序齊整美觀。原來的活動區間在GC後就變成了徹底空的空閒區間,等待下一次GC把活的對象被copy進來。

評價

  • 【優勢】GC後的內存齊整,不產生內存碎片。
  • 【缺點】GC要使用兩倍的內存,或者說致使堆只能使用被分配到的內存的一半,這個算法對空間要求過高!若是存活的對象較多,則意味着要複製不少對象而且要維護大量對象的內存地址,因此存活的對象數量不能太多,不然效率也會很低。

複製算法複製移動的對象是【活着的對象】,對於垃圾對象(不被標記的對象)則直接回收。

2.3 標記-整理算法(Mark-Compact)

這個算法則是對上面兩個算法的綜合結果。也分爲兩個階段:

1)標記:這個階段和標記-清除Mark-Sweep算法同樣,遍歷GC ROOTS並標記存活的對象。

2)整理:移動全部活着的對象到內存區域的一側(具體在哪一側則由GC實現),嚴格按照內存地址次序依次排列活着的對象,而後將最後一個活着的對象地址之後的空間所有回收。

評價

  • 【優勢】內存空間利用率高,消除了複製算法內存減半的狀況;GC後不會產生內存碎片。

  • 【缺點】須要遍歷標記活着的對象,效率較低;複製移動對象後,還要維護這些活着對象的引用地址列表。

2.4 分代回收算法(Generational Collecting)

分代回收算法就是如今JVM使用的GC回收算法。

2.4.1簡要說明

1)先來看看簡單化後的堆的內存結構:

Java堆 = 年老代 + 年輕代
(空間大小比例通常是3:1)

年輕代 = Eden區 + From Space區 + To Space區
(空間大小比例通常是8:1:1)

2)按照對象存活時間長短,咱們能夠把對象簡單分爲三類:

  • 短命對象:存活時間較短的對象,如中間變量對象、臨時對象、循環體建立的對象等。這也是產生最多數量的對象,GC回收的關注重點。

  • 長命對象:存活時間較長的對象,如單例模式產生的單例對象、數據庫鏈接對象、緩存對象等。

  • 長生對象:一旦建立則一直存活,幾乎不死的對象。

3)對象分配區域
短命對象存在於年輕代,長命對象存在於年老代,而長生對象則存在於方法區中。
因爲GC的主要內存區域是堆,因此GC的對象主要就是短命對象和長命對象這類壽命「有限」的對象。

2.4.2 分代回收的GC類型

針對HotSpot VM的的GC其實準確分類只有兩大種:

1)Partial GC:部分回收模式

  • Young GC:只收集young gen的GC。和Minor GC同樣。
  • Old GC:只收集old gen的GC。只有CMS的concurrent - collection是這個模式
  • Mixed GC:收集整個young gen以及部分old gen的GC。只有G1有這個模式

2)Full GC:收集整個堆,包括young gen、old gen,還有永久代perm gen(若是存在的話)等全部部分的模式。同Major GC。

3)觸發時機
HotSpot VM的串行GC的觸發條件是:
young GC:當young gen中的eden區分配滿的時候觸發。

full GC:當準備要觸發一次young GC時,若是發現統計數聽說以前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉爲觸發full GC;或者,若是有perm gen的話,要在perm gen分配空間但已經沒有足夠空間時,也要觸發一次full GC;或者System.gc()、heap dump帶GC,默認也是觸發full GC。

併發GC的觸發條件就不太同樣。以CMS GC爲例,它主要是定時去檢查old gen的使用量,當使用量超過了觸發比例就會啓動一次CMS GC,對old gen作併發收集。

2.4.3 年輕代GC過程

當須要在堆中建立一個新的對象,而年輕代內存不足時觸發一次GC,在年輕代觸發的GC稱爲普通GC,Minor GC。注意到年輕代中的對象都是存活時間較短的對象,因此適合使用複製算法。這裏確定不會使用兩倍的內存來實現複製算法了,牛人們是這樣解決的,把年輕代內存組成是80%的Eden、10%的From Space和10%的To Space,而後在這些內存區域直接進行復制。

剛開始建立的對象是在Eden中,此時Eden中有對象,而兩個survivor區沒有對象,都是空閒區間。第一次Minor GC後,存活的對象被放到其中一個survivor,Eden中的內存空間直接被回收。在下一次GC到來時,Eden和一個survivor中又建立滿了對象,這個時候GC清除的就是Eden和這個放滿對象的survivor組成的大區域(佔90%),Minor GC使用複製算法把活的對象複製到另外一個空閒的survivor區間,而後直接回收以前90%的內存。周而復始。始終會有一個10%空閒的survivor區間,做爲下一次Minor GC存放對象的準備空間。

要完成上面的算法,每次Minor GC過程都要知足:
存活的對象大小都不能超過survivor那10%的內存空間,否則就沒有空間複製剩下的對象了。可是,萬一超過了呢?前面咱們提到過年老代,對,就是把這些大對象放到年老代。

2.4.4 年老代GC

什麼樣的對象能夠進入年老代呢?以下:

  • 在年輕代中,若是一個對象的年齡(GC一次後還存活的對象年歲加1)達到一個閾值(能夠配置),就會被移動到年老代。
  • Survivor中相同年齡的對象大小總和超過survivor空間的一半,則不小於這個年齡的對象都會直接進入年老代。
  • 建立的對象的大小超過設定閾值,這個對象會被直接存進年老代。
  • 年輕代中大於survivor空間的對象,Minor GC時會被移進年老代。

年老代中的對象特色就是存活時間較長,並且沒有備用的空閒空間,因此顯然不適合使用複製算法了,這個時候使用標記-清除算法或者標記-整理算法來實現GC。負責年老代中GC操做的是全局GC,Major GC,Full GC。

何時觸發Major GC呢?
在Minor GC時,先檢測JVM的統計數據,查看歷史上進入老年代的對象平均大小是否大於目前年老代中的剩餘空間,若是大於則觸發Full GC。


3.GC執行機制

3.1串行GC

在搜索掃描和複製過程都是採用單線程實現,適用於單CPU、新生代空間較小或者要求GC暫停時間要求不高的地方。是client級別的默認方式。

3.2並行GC

在搜索掃描和複製過程都是採用多線程實現,適用於多CPU、或者要求GC暫停時間要求高的地方。是server級別的默認方式。

3.3同步GC

同時容許多個GC任務,減小GC暫停時間。主要應用在實時性要求重於整體吞吐量要求的中大型應用,即便如此,下降中斷時間的技術仍是會致使應用程序性能的下降。


4.內存調優

JVM內存調優,主要是減小GC的頻率和減小Full GC的次數,Full GC的時候會極大地影響系統的性能。因此在此基礎上,更加要關注會致使Full GC的狀況。

4.1 容易致使Full GC的狀況

  • 年老代空間不足
    1)分配足夠大空間給old gen。
    2)避免直接建立過大對象或者數組,不然會繞過年輕代直接進入年老代。
    3)應該使對象儘可能在年輕代就被回收,或待得時間儘可能久,避免過早的把對象移進年老代。

  • 方法區的永久代空間不足
    1)分配足夠大空間給。
    2)避免建立過多的靜態對象。

  • 被顯示調用System.gc()
    一般狀況下不要顯示地觸發GC,讓JVM根據本身的機制實現。

4.2 JVM堆內存分配問題討論

4.2.1 年輕代太小(年老代過大)

  • 致使頻繁發生GC,增大系統消耗
  • 容易讓普通大文件直接進入年老代,從而更容易誘發Full GC。

4.2.2 年輕代過大(年老大太小)

  • 致使年老代太小,從而更容易誘發Full GC。
  • GC耗時增長,下降GC的效率。

4.2.3 Eden過大(survivor太小)

Minor GC時容易讓普通大文件直接繞過survivor進入年老代,從而更容易誘發Full GC。

4.2.4Eden太小(survivor過大)

致使GC頻率升高,影響系統性能。

4.3 調優策略

  • 保證系統吞吐量優先
  • 減小GC暫停時間優先

5 JVM常見配置選項

總結一下常見配置。

堆設置

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:設置年輕代大小

-XX:NewRatio=n:設置年輕代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代和的1/4

-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5

-XX:MaxPermSize=n:設置持久代大小

收集器設置

-XX:+UseSerialGC:設置串行收集器

-XX:+UseParallelGC:設置並行收集器

-XX:+UseParalledlOldGC:設置並行年老代收集器

-XX:+UseConcMarkSweepGC:設置併發收集器

垃圾回收統計信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

並行收集器設置

-XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。

-XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間

-XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)

併發收集器設置

-XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU狀況。

-XX:ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。

參考:
http://www.mamicode.com/info-detail-1028149.html
http://www.importnew.com/17770.html
http://www.cnblogs.com/sunfie/p/5125283.html
http://longdick.iteye.com/blog/474764
http://blog.csdn.net/z69183787/article/details/51606410

相關文章
相關標籤/搜索