Java虛擬機學習筆記(六)——GC機制補充(安全點、OopMap、觸發條件、收集器)

GC觸發條件

Minor GC觸發條件

  • 當Eden區滿時,觸發Minor GC。

Full GC觸發條件

  • 調用System.gc時,系統建議執行Full GC,但是不是必然執行
  • 通過Minor GC後進入老年代的平均大小大於老年代的可用內存
    當要觸發一次Minor GC時,如果發現統計數據中Minor GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發Minor GC,而是直接轉爲Full GC。
  • 方法區空間不足
    運行時數據區域中的方法區,在HotSpot虛擬機中又被習慣稱爲永生代或者永生區,Permanet Generation中存放的爲一些class的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被佔滿,在未配置爲採用CMS GC的情況下也會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會拋出如下錯誤信息:
    java.lang.OutOfMemoryError: PermGen space
    爲避免Perm Gen佔滿造成Full GC現象,可採用的方法爲增大Perm Gen空間或轉爲使用CMS GC。
  • 老年代空間不足,和第二點類似
    舊生代空間只有在新生代對象轉入及創建爲大對象、大數組時纔會出現不足的現象,當執行Full GC後空間仍然不足,則拋出如下錯誤: java.lang.OutOfMemoryError: Java heap space 爲避免以上兩種狀況引起的FullGC,調優時應儘量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。

OopMap

OopMap 用於枚舉GCRoots

當垃圾回收時,收集線程會對棧上的內存進行掃描,看看那些位置上存儲了Reference類型。如果發現了某個位置上存儲的是Reference類型,就意味着這個引用所指向的對象在這一次垃圾回收過程中不能夠回收。

棧上的本地變量表裏面只有一部分數據是Reference類型的,爲了避免每次都掃描整個棧,所以採用空間換時間的策略。在某個時候(安全點)把棧上代表引用的位置記錄下來,GC時直接讀取,避免了全部掃描。 HotSpot虛擬機採用了一種叫做OopMap的數據結構來記錄這些引用(OopMap也幫助HotSpot實現了準確式GC),OopMap記錄了棧上本地變量到堆上對象的引用關係,這些引用指向的對象不能夠回收,並且可以作爲根節點來進行可達性分析,查找出不能夠回收的對象。

安全點

safepoint 安全點是指一些特定的位置,當線程運行到這些位置時,線程的一些狀態可以被確定,比如記錄OopMap的狀態,從而確定GC Root的信息,使JVM可以安全的進行一些操作,比如開始GC。

上面講到了爲了快點進行可達性的分析,使用了一個引用類型的映射表,可以快速的知道對象內或者棧和寄存器中哪些位置是引用了。

但是隨着而來的又有一個問題,就是在方法執行的過程中, 可能會導致引用關係發生變化,那麼保存的OopMap就要隨着變化。如果每次引用關係發生了變化都要去修改OopMap的話,這又是一件成本很高的事情。所以這裏就引入了安全點的概念。

什麼是安全點?OopMap的作用是爲了在GC的時候,快速進行可達性分析,所以OopMap並不需要一發生改變就去更新這個映射表。只要這個更新在GC發生之前就可以了。所以OopMap只需要在預先選定的一些位置上記錄變化的OopMap就行了。這些特定的點就是SafePoint(安全點)。由此也可以知道,程序並不是在所有的位置上都可以進行GC的,只有在達到這樣的安全點才能暫停下來進行GC。

既然安全點決定了GC的時機,那麼安全點的選擇就至爲重要了。安全點太少,會讓GC等待的時間太長,太多會浪費性能。所以安全點的選擇是以程序“是否具有讓程序長時間執行的特徵”爲標準的,所以我們這裏瞭解一下結果就行了。一般會在如下幾個位置選擇安全點:

  1. 循環的末尾 (防止大循環的時候一直不進入safepoint,而其他線程在等待它進入safepoint)
  2. 方法返回前
  3. 調用方法的call指令之後
  4. 拋出異常的位置

新生代收集器

並行、併發

  • 並行:指多條垃圾收集線程並行工作,此時用戶線程任然處於等待狀態
  • 併發:指用戶線程與垃圾收集線程同時執行。

吞吐量

吞吐量 = CPU運行在用戶代碼時間 / CPU總消耗時間

CPU總消耗時間 = 運行用戶代碼時間 + 垃圾收集時間

Serial(串行)收集器

Serial(串行)收集器,它採用複製算法新生代收集器,它是一個單線程的收集器,只會使用一個CPU或一條收集線程完成垃圾收集工作。

它在進行垃圾回收時,必須暫停其他所有的工作線程,直至收集結束(Stop The World)。
在這裏插入圖片描述
上圖是Serial 收集器(老年代採用Serial Old收集器)的運行過程。

特點:HotSpot虛擬機運行在Client模式下的默認的新生代收集器。簡單高效(與其他收集器的單線程相比),對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得更高的單線程收集效率。

ParNew 收集器

ParNew收集器是Serial收集器的多線程版本,他也是一個新生代的收集器。除了使用多線程進行垃圾回收外,其餘行爲和Serial收集器完全相同。

在這裏插入圖片描述
上圖是ParNew的工作過程。

除了Serial收集器外,目前只有它能和CMS收集器配合工作。

特點:單CPU效率低於Serial收集器,因爲存在線程交互帶來的開銷。多CPU環境下,能更好的利用系統資源。

Parallel Scavenge 收集器

Parallel Scavenge收集器是一個並行的多線程新生代收集器,它使用複製算法。

Parallel Scavenge收集器的目標是達到一個可控制的吞吐量。

停頓時間越短就越適合需要與用戶交互的程序,良好的響應速度能提升用戶體驗。而高吞吐量則可以高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務。

Parallel Scavenge收集器除了提供可以精確控制吞吐量的參數,還提供了一個參數-XX:+UseAdaptiveSizePolicy,打開參數後,就不需要手工指定新生代的大小、Eden和Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種方式稱爲GC自適應的調節策略(GC Ergonomics)。自適應調節策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。

老年代收集器

Serial Old收集器

Serial Old 是 Serial收集器的老年代版本,它同樣是一個單線程收集器,使用標記-整理算法。

在這裏插入圖片描述

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和標記-整理算法。

在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。
在這裏插入圖片描述

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,非常重視服務的響應速度。它是基於標記-清除算法實現的。

收集流程:

  • 初始標記:僅僅標記GC Roots能直接關聯到的對象,速度快
  • 併發標記::進行GC Roots Tracing的過程,耗時長
  • 重新標記:爲了修正併發標記期間程序繼續運作導致標記產生變動的部分對象標記記錄
  • 併發清除

併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,總體上來說,CMS收集器的內存回收過程是與用戶線程一起併發執行的。

在這裏插入圖片描述
優點:併發收集、低停頓

缺點:

  • 併發階段,它雖然不會導致用戶線程停頓,但會因爲佔用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。
  • 由於CMS併發清理階段用戶線程還在運行着,伴隨程序運行自然就還會有新的垃圾不斷產生。這一部分垃圾出現在標記過程之後,CMS無法再當次收集中處理掉它們,只好留待下一次GC時再清理掉。
  • 標記-清除算法導致的空間碎片

G1收集器

與其他GC收集器相比有以下特點:

  • 並行與併發:G1能充分利用多CPU多核優勢,使用多個CPU縮短Stop The World停頓時間。他可以併發的方式讓Java程序繼續執行。
  • 分代收集
  • 空間整合:G1基於標記-整理算法,G1運行期不會產生內存空間碎片。
  • 可預測的停頓:降低停頓時間是G1和CMS共同的關注點,但G1除了降低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片段內,消耗在GC上的時間不得超過N毫秒

跨越整個堆內存

G1不再像其他收集器那樣收集的範圍是新生代或老年代。G1的內存佈局和其他收集器有很大的區別。

他將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留新生代和老年代,但是他們不再是物理隔離的了,而是一部分Region的集合。

建立可預測的時間模型

G1收集器有計劃的在整個Java堆中進行全區域的垃圾收集。

G1追蹤各個Region裏面的垃圾堆積的價值大小(回收所獲空間大小以及回收所需時間),在後臺維護一個優先列表,優先回收價值最大的Region。

這種Region的劃分內存空間以及優先級區域的回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效率。

避免全堆掃描 Remembered Set

Region不可能是孤立的,一個對象分配在某個Region中,可以與整個Java堆任意的對象發生引用關係。在做可達性分析確定對象是否存活的時候,需要掃描整個Java堆才能保證準確性,這會減低GC的效率。

爲了避免全堆掃描發生,虛擬機爲G1每個Region維護了一個與之對應的Remembered Set。虛擬機發現程序在對Reference類型的數據進行寫操作時,會檢查Reference引用對象是否處於不同的Region中,如果是,就把相關引用記錄到被引用對象所屬的Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉範圍中加入RememberedSet 即可保證不對全堆掃描也不會有遺漏。

G1收集器運作步驟

  • 初始標記:僅僅標記GC roots能直接關聯到的對象。
  • 併發標記:從GC Root 開始對堆對象進行可達性分析,可與用戶程序併發執行。
  • 最終標記:修正併發期間用戶程序繼續運作導致標記產生變動的那一部分標記內容。
  • 篩選回收:首先對各個Region中的回收價值和成本排序,根據用戶期望的GC停頓時間進行制定回收計劃。

在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章