GC

什麼是GC?

GC的本質: 從GC Roots開始,沿着引用鏈找到全部的能夠到達的對象(reachable objects),並把它們標記爲活動對象(alived objects),這個過程叫可達性分析。java

垃圾回收器跟蹤全部仍在使用的對象,並將其他對象標記爲垃圾。程序員

內存管理的兩個流派

手動管理內存(C/C++)

正如咱們所看到的,很容易忘記釋放內存。內存泄漏是常見的問題。只有修改代碼才能真正解決問題。所以,更好的方法是自動回收未使用的內存,徹底消除人爲錯誤的可能性。這種自動化稱爲垃圾收集(簡稱GC)算法

自動內存管理(幾乎全部的現代編程語言)

  • 引用計數Reference Counting)編程

    綠色的雲代表程序員指向的對象仍在使用中。 從技術上講,這些多是諸如當前正在執行的方法中的局部變量或靜態變量之類的東西編程語言

    藍色圓圈是內存中的活動對象,其中的數字表示其引用計數。性能

    灰色圓圈是還沒有從仍在顯式使用的任何對象中引用的對象(這些對象由綠雲直接引用)。 所以,灰色物體是垃圾,能夠由垃圾收集器清理spa

    引用計數的致命缺點—-循環引用線程

    因爲循環引用,其引用計數不爲零,紅色對象其實是應用程序不使用的垃圾。可是因爲引用計數的限制,仍然存在內存泄漏3d

  • 標記清除(Mark and Sweep)code

    從一組預先定義好的根節點(GC root)開始,把全部可達的對象都打上標記,而後把不可達的對象(垃圾)所有清除掉。

    JVM用來跟蹤全部可訪問(活動)對象並確保不可訪問對象聲明的內存能夠重用的方法稱爲標記和清除算法。 標記清除主要分爲兩步:

    1. Marking: 從GC Roots開始遍歷全部可訪問的對象,並在本機內存中保存這些對象的有關記錄
    2. Sweeping: 清除不可達對象佔用的內存地址,以確保下一次能夠從新分配這些內存

碎片化問題

長期工做的內存會出現碎片化,總可用空間仍然足夠,可是沒法分配大對象

每當進行垃圾清理時,JVM必須確保這些垃圾對象所佔的內存能夠被重用,可是這可能會致使內存碎片化問題,這和磁盤碎片相似,會致使兩個問題:

  1. 建立新對象時,JVM會分配連續的內存,所以若是碎片化到達某種程度,致使沒有任何單獨的空閒碎片小都不足以容納新建立的對象,則會發生分配錯誤
  2. 內存的Write操做,會由於很難找到下一個足夠大的內存塊,而變的更加耗時

爲了不這些問題,JVM會確保碎片化不會到失控的地步,由於垃圾回收不只要把垃圾清除掉,還要作內存碎片整理,把剩下的非垃圾對象所佔的內存壓縮的更緊密(內存地址更連續),方便後續分配較大的對象。

Java中的引用類型

  • 強引用 Strong Reference : 你用到的都是強引用
  • 軟引用 Soft Reference: 內存不夠的時候會回收
  • 弱引用 Weak Reference: GC碰到它們就會回收
  • 影子引用 Phantom Reference: 只能拿到影子,拿不到引用自己

對象的分代假設

研究代表,絕大多數對象的生命週期都很短(朝生夕死)

內存分代

有了這些獨立的、單獨的可清理區域,就可使用大量不一樣的算法,這些算法在提升GC性能方面取得了長足的進步

  • 年輕代 (Young Generation)
    • Eden: 伊甸園
    • Survivor: 倖存區
  • 老年代 (Old Generation/Tenured)
  • 永久代(Permanent Generation)

任何新建立的對象都會先進入Eden(上帝創造亞當夏娃,讓他們結爲夫妻生活在伊甸園), 而後經歷了一場洪水或災難以後,進入了Survivor (倖存區),當一個對象經歷過不少災難(GC)後,它會變得很資深,就進入了Tenured(老年代)

年輕代

  • Eden(伊甸園)
    • 能夠分爲多個 Thread Local Allocation Buffer

Java中新建立的對象會分配到Eden中。因爲會有多個線程同時建立多個對象的場景,所以Eden 進一步分爲一個或多個駐留在Eden空間中的Thread Local Allocation Buffer 簡稱(TLAB)。 這些緩衝區容許JVM在相應的TLAB中直接在線程內分配大多數對象,從而避免了和其它線程的同步問題。

TLAB中沒有足夠的空間時,該對象會被分配到Eden的共享空間(如上圖中的Common area), 此時會觸發Young GC(年輕代的垃圾回收)來釋放更多的空間,若是垃圾回收後仍沒有足夠的空間,這些對象會被放入 老年代中。

Eden發生GC時,GC會從Gc Roots開始,沿着引用鏈訪問全部可達對象,並將其標記爲活動對象。

Marking phase(標記階段)完成後,Eden中的全部活動對象會被複制到一個Survivor中,此時整個Eden被認爲是空的,能夠用來從新分配對象。

這種方法叫作"Mark And Copy": 標記活的對象,而後把他們移動到Survivor中去。

  • Survivor(倖存區)
    • Young GC發生時,整個年輕代中的對象會進入其中一個Survivor
    • 足夠老的對象會提高到老年代

緊挨着Eden的是兩個叫作fromandto的生存空間。須要注意的是,兩個Survivor中的一個老是空的。

在兩個Survivor之間複製活動對象的過程會重複幾回,直到某些對象被認爲已經足夠成熟, 就能夠晉升到 Tenured(老年代)

如何判斷活動對象是否"足夠老"到能夠晉升到Tenured

在每一代對象完成一個GC以後,那些仍然活着的對象的年齡就會增長。每當年齡超過某個***閾值***時,對象將被提高到老年代

實際的tenuring閾值由JVM動態調整,但指定-XX:+maxtenuringthreshold設置上限。設置- xx:+MaxTenuringThreshold=0 會致使活動對象由Eden直接晉升到老年區,無需在Survivor之間來回複製活動對象。默認狀況下,現代JVM上的這個閾值設置爲15個GC週期。這也是HotSpot中的最大值。

老年代

  • 老年代相對較大
  • 存儲足夠年老的對象(經歷過15次GC)
  • 發生GC的頻率較低
    • 清除垃圾
    • 壓緊內存

永久代/元空間

  • Java8以前:永久代

    • 是堆得一部分

    • 存儲類數據、字符串常量

    • java.lang.OutOfMemoryError:Permgenspace

      java -XX:MaxPermSize=256m com.mycompany.MyApplication

  • Java8以後:元空間

    • 不是堆得一部分

    • 除非特殊指定,不然空間大小沒有上限

    • java.lang.OutOfMemoryError:Metaspace

      java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication

GC的種類

  • Minor GC/Young GC

    • JVM沒法爲新對象分配空間時觸發,例如Eden變得滿了。所以,分配率越高,發生Minor GC的頻率就越高
    • 會觸發STW(暫停應用程序線程)

  • Major GC vs Full GC

    • 這兩個概念沒有明確的定義,這裏闡述通常理解
      • Major GC 清除老年代
      • Full GC 清除整個堆--包括年輕代和老年代

GC的過程

  1. 標記階段(Marking Reachable Objects)

    JVM中使用的全部現代GC算法都是從找到全部alive objects(活動對象)開始。

    下面這張圖很好的解釋了JVM的內存分佈(綠色雲表明GC Roots, 藍色圈表明可達對象,灰色圈表明不可達對象):

    首先GC定義了一些指定的對象做爲GC Roots:

    • 活的線程
    • 類的靜態成員
    • 線程方法棧索所引用的對象
    • JNI引用的對象
    • 分代GC時其它代的對象

    而後從GC Roots開始沿着引用鏈作可達性分析,找到全部可達對象並將它們標記爲活動對象(alive objects)。

    標記階段有幾個重要方面須要注意:

    • 上面說到可達性分析,可是分析確定須要時間,因此GC須要線程中止才能開始收集垃圾, 由於線程不中止,引用將一直在變更,就不能準確的計數或標記對象。當線程被臨時中止,JVM就能夠進行管理活動了,這種狀況叫Stop The World ,簡稱 STW(舉個例子:就像水塘裏有活魚有死魚,你要找到全部的活魚並標記,但若是魚一直在遊動,你可能永遠也標記不完)
    • 這個暫停的持續時間既不取決於堆中對象的總數,也不取決於堆的大小,而是取決於alive objects的數量。所以,增長堆的大小不會直接影響標記階段的持續時間
  2. 清除階段(Removing Unused Objects)

    不一樣的GC算法對Unused Objects對象的刪除有所不一樣,但能夠分爲三類:

    • Sweep
    • Compact
    • Copy

    Sweep(Mark and Sweep): 標記-清除

    Compact(Mark-Sweep-Compact):標記-清除-壓縮

    老年代中使用

    Copy(Mark-and-Copy): 標記-清除-複製

    年輕代中使用(Eden—>Survivor)

    因此一個完整的GC過程以下:

    1. STW (Stop The World)
    2. 清除垃圾
    3. 壓縮內存/複製到另外一塊內存
相關文章
相關標籤/搜索