JVM概覽

在這裏插入圖片描述

GC是什麼?

清理jvm運行時數據區域-堆中的無用對象.

清理的是哪部分內存?

jdk1.7: 方法區,堆空間 1.8 : 堆空間,元空間.

爲什麼需要清理?

內存有限.

怎麼清理?

判斷對象是否需要被清理
  • 引用計數法

無法解決循環引用問題.

  • 可達性分析算法

從"GC Roots"的對象作爲起始點,延引用鏈搜索.如果一個對象沒有任何任何GCRoots能夠訪問到.那這個對象就是不可達狀態.即可回收.

GC Roots
  • 虛擬機棧(棧中的本地變量表)中引用的對象.
  • 方法區中類靜態屬性引用的對象.
  • 方法區中常量引用的對象.
  • 本地方法棧中引用的對象.
引用狀態
  • 強引用

Object a=new Object(); 在代碼有效範圍內時,對象永遠不會被回收.

  • 軟引用
SoftReference<String> stringSoftReference = new SoftReference<>("hello, World");

在jvm即將發生OOM的時候會回收這些對象.用作臨時緩存.

  • 弱引用
WeakReference<String> weakReference = new WeakReference<String>(new String("hello, World"));

存活到下一次垃圾回收之前.

  • 虛引用
//必須配合引用隊列來使用虛引用
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
System.out.println(pr.get());
//===============
輸出結果:
null

配合finalize函數使用,在finalize函數被調用後,虛引用就會被加入到引用隊列中,可以通過檢查引用隊列判斷對象的回收狀態.

軟引用和弱引用

如果有大量的數據要加載到內存中,有可能會造成OOM,造成其他業務異常,爲了避免這種事情,可以使用軟/弱引用降低OOM風險

常用GC算法
  • 標記-清除

    1. 標記出需要回收的對象.
    2. 清除被標記的對象

在這裏插入圖片描述

效率低, 產生大量不連續內存碎片.

  • 複製
    在這裏插入圖片描述

沒有了內存碎片確要浪費一半的內存空間,而且如果存活對象很多的是否,會產生大量的複製工作. 代價太高.在young(新生代)中採用了8:1:1的比例分配eden,from,to,只浪費了young區的1/10;

  • 標記-整理
    在這裏插入圖片描述

因複製算法的浪費內存空間,大量複製對象特性,無法適用於老年代,標記整理算法就是爲了補充複製算法的不足,但是還是存在對象複製的情況.

  • 分代收集

根據對象存活週期不同將內存區域劃分: young,old, 然後對之前的算法進行一個適當的組合使用, 新生代對象死的快就適合用複製算法,可以減少存活對象的複製工作. 老年代對象存活率較高,需要空間比較大,無法提供很多的浪費空間不適合使用複製算法,可以使用標記-清除/標記-整理算法.

HotSpot 的算法實現
  • 判斷對象的存活狀態,在HotSpot中使用的是可達性分析算法

    1. 枚舉根節點, 因爲空間很大,不可能去檢查每一個引用,通過一組OopMap的數據來判斷對象的存活狀態,在類加載完成的時候就維護了這個數據 ,JIT編譯時,會在所有方法返回之前以及循環跳轉、異常跳轉之前放置Safepoint,並且爲每個Safepoint都生成一些信息存儲哪些地方是引用(OopMap).通過直接掃描這些維護好的數據就可以快速的尋找到存活的對象.

    可達性分析算法需要在一個一致性的快照中進行,因爲運行中的系統引用關係在不停的發生變化,所以就需要將系統凍結在某個時間點.(Stop The World).

    1. Safepoint :

    內存分配的地方(allocation,即new一個新對象的時候)

    長時間執行區塊結束的時刻(如方法調用,循環跳轉等)

    之所以只在特定的位置放置safepoint,是因爲OopMap要佔用空間,如果設太多safepoint那麼佔用空間會太大;再者,safepoint會影響優化,如果某個無用的值處設置了safepoint,那麼JIT就無法優化掉這些無用變量,這會影響性能。

    HotSpot JVM在通過JIT編譯時,會在所有方法返回之前以及循環跳轉、異常跳轉之前放置Safepoint,並且在每個Safepoint都生成一些信息存儲哪些地方是引用(OopMap),以便JVM能找到需要的引用。

    那麼如何確保GC時所有線程都到達GC Safepoint呢?有兩種方法:搶佔式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension)。

    搶佔式中斷不需要線程的執行代碼去主動配合,當觸發GC時,JVM會中斷所有線程,然後依次檢查每個線程中斷的位置是否爲Safepoint,如果不是則恢復線程,讓它執行至Safepoint再進行終端。

    大部分JVM實現(如HotSpot JVM)都是採用主動式中斷,即GC需要中斷線程的時候,它僅僅簡單地設個標誌,執行線程會主動輪詢這個標誌位,如果標誌位就緒的話就自行中斷。

    1. SafeRegion:

    只有GC Safepoint是不足的,因爲我們發現,有一種情況,線程無法響應JVM的中斷請求,也無法去輪詢標誌位:

    線程處於阻塞或等待狀態

    對於這種情況,引入了safe-region的概念。

    Safe-Region是指在代碼片段中,引用關係不會發生變化,因此GC可以隨心所欲地在任何地方執行。在線程執行到Safe Region裏面的代碼時,首先標識自己已經進入了Safe Region,那樣當這段時間裏JVM要發起GC,就不用管標識自己爲Safe Region狀態的線程了。在線程要離開Safe Region時,它要檢查系統是否已經完成了根節點枚舉(或者是整個GC過程),如果完成了,那線程就繼續執行,否則它就必須等待直到收到可以安全離開Safe Region的信號爲止。

    另外,當一個線程在執行native方法時,由於此時該線程在執行JVM管理之外的代碼,不能對JVM的執行狀態做任何修改,因而JVM要進入safepoint不需要關心它。所以也可以把正在執行native函數的線程看作“已經進入了safepoint”,或者把這種情況叫做“在safe-region裏”。

常用垃圾收集器
新生代垃圾收集器
  • serial

jdk1.3.1之前的唯一收集器,單線程收集,並且全程StopTheWorld.

  • parNew

serial的多線程版本,多用於配合CMS(Current Mark Sweep)收集器工作.CMS算是HotSpot中第一個併發收集器(能和用戶線程並行工作),在單核CPU中,serial效果比parNew好,減少線程切換的工作.它默認開啓的垃圾收集線程數是和CPU核數相同,可以通過參數: -XX:ParallelGCThreads 限制最大線程數.

Parallel: 多條垃圾收集線程同時工作. 用戶線程處於暫停狀態.

Concurrent: 垃圾收集線程和用戶線程同時工作.

  • Parallel scavenge

新生代收集器,和parNew類似的並行多線程收集器, 它關注的是系統的可控制的吞吐量(Throughput). 吞吐量=運行用戶代碼的cpu時間/(用戶代碼cpu時間+垃圾收集cpu時間),適用於高效利用cpu時間的場景(後臺運算),不適合用於和用戶交互的場景.兩個參數控制吞吐量: 最大的系統停頓時間: -XX:MaxGCPauseMillis(時間減小是以縮短吞吐量和新生代空間爲代價的,過小會導致系統頻繁發生垃圾回收) 吞吐量大小: -XX:GCTimeRatio (0-100), -XX:+UseAdaptiveSizePolicy 自適應調節策略開關,系統會根據系統運行情況自動分配 eden,from,to,-XX:PretenureSizeThreshold(進入老年代的分界年齡).


老年代垃圾收集器
  • Serial old

Serial 的老年代版本收集器 Serial old.是個單線程收集器(標記整理算法),在jdk1.5以及之前用來配合Parallel scavenge收集器工作. CMS收集器的後備預案.CMS會發生Concurrent Mode Failure.

  • parallel old

parallel scavenge的老年代版本(標記整理算法),jdk1.6開始提供.用於和parallel scavenge配合工作.

  • cms

CMS(concurrent mark sweep): 是一款以獲取最短回收停頓時間爲目標的垃圾收集器,用於提高服務器響應速度.

使用的是標記清除算法.

工作四個步驟:

  1. 初始標記(initial mark): 僅僅標記gc roots能直接關聯到的對象.工作量小,停頓時間短.
  2. 併發標記(concurrent mark): gc roots tracing, 和用戶線程同時工作.
  3. 重新標記(remark): 修正上一步過程中用戶產生的對象引用變動.工作量小,停頓時間短.
  4. 併發清除(concurrent sweep): 清理垃圾對象.

併發階段由於會佔用資源, 會降低系統的吞吐量;

無法處理浮動垃圾(floating garbage, 在併發清理階段產生的垃圾) 可能會出現concurrent mode failure,從而產生另外一次full gc.因爲併發清除階段要預留一部分空間給用戶線程使用,如果這個預留空間不足就會產生 concurrent mode failure錯誤,這時就啓用serial old收集器來重新進行老年代的垃圾回收工作.這就會產生嚴重的stop the world. 預留空間閾值: -XX:CMSInitiatingOccupancyFraction值太高容易出現concurrent model failure錯誤,太低性能下降明顯.

算法原因會導致內存碎片. 可以使用-XX:+UseCMSCompactAtFullCollection開關,默認開啓.在full gc之前開啓內存碎片整理.又會產生stop the world.

-XX:CMSFullGCsBeforeCompaction 不整理full gc次數後來一次整理過程.默認值爲0,每次full gc 都進行內存碎片整理.

  • g1

特徵

  • 並行併發

利用多核cpu特性縮短停頓時間, 利用併發特性讓gc和用戶線程同時工作

  • 分代收集

用不同的方式處理不同年齡的對象,以獲得最佳的收集效果.

  • 空間整合

從整體上看是標記整理算法,從兩個region中看是複製算法實現的.這兩種算法都不會產生內存碎片.避免因爲大對象的內存分配問題導致頻繁發生下一次gc.

  • 可預測停頓

通過預設可接受停頓時間合理安排回收region.

工作原理圖示
在這裏插入圖片描述

Region

​ G1和其他收集器的不同之處在於其將內存分爲大小相同的若干個獨立的 region,新生代和老年都是由多個不連續的Region組成.G1會維護一個垃圾堆積的價值大小排序的優先列表,每次根據允許的收集時間,優先回收價值(回收後所獲得的空間大小,回收需要的時間的經驗值)最大的Region,從而保證在最短的時間內最大化收集效率.可以通過參數: -XX:G1HeapRegionSize 1-32M,2的指數,如果不指定大小通過: size =(堆最小值+堆最大值)/ TARGET_REGION_NUMBER(2048) ,然後size取最靠近2的冪次數值, 並將size控制在[1M,32M]之間。

​ Region和Region之間發生引用的處理(在之前的分代收集中新生代和老年代也是如此): 在可達性分析中判斷對象是否存活的時候必須要從根節點開始遍歷所有節點,效率極其低下,爲了解決這個問題虛擬機通過維護一個Remembered Set來避免全堆掃描,如圖所示,在發生Reference 類型數據寫操作時: b.a=a;檢查a和b是否在同一個region中,如果不在,在a的Remembered Set中記錄引用關係,在遍歷GC root時,加入Remembered Set即可.

回收過程

  1. 初始標記
  2. 併發標記
  3. 重新標記
  4. 清除
  5. 轉移回收

特性

  1. 根據設定的目標暫停時間自動調整新生代的空間大小和總堆大小
  2. 分區回收,將存活對象複製到新的空間分區, 天然壓縮方案(局部)
  3. 混合收集模式,回收可能只包含新生代和同時包含新生代和老年代

Region

可以通過參數 -XX:G1HeapRegionSize=n( 1-- 32 之間 2 的次冪),默認是2048個region.

Card

Region的進一步細分,分爲若干個512Byte大小的Card, Card爲堆內最小粒度的可用內存. 所有Region的Card都記錄在Global Card Table中,對象的分配都是分配在物理上連續的若干個卡片,每次的內存回收就是對指定Region的Card處理.

分代

G1的分代是邏輯上的分代,不需要固定的物理上連續內存作爲新生代或者老年代,其大小是根據目標暫停時間動態調整的,新生代內存的初始空間會在-XX:G1NewSizePercent(默認整堆5%) 和最大空間 -XX:G1MaxNewSizePercent(默認60%)直接動態變化, 變化的依據是: 目標暫停時間-XX:MaxGCPauseMillis(默認200ms)/RSet計算得到. 不能設置固定大小的年輕代大小,如果設定目標暫停時間將沒有意義.

LAB

Local allocation buffer ,本地線程和gc,promotion都有自己的獨佔region分別稱作: TLAB,GCLAB,PLAB.

Humongous Region

在新分配對象的大小超過Region對象的一半大小時,因爲大對象的複製成本極高,需要一開始就分配在老年代中,如果跨Region的時候需要尋找連續的Region,尋找物理上連續的Region就需要掃描整個堆,這種對象jvm會實時監測它的引用,如果不存在引用了,在新生代GC的時候,立馬回收這種對象.(避免產生這種對象)

Remembered Set

在g1之前的垃圾收集器中,值可達性分析中都是stw掃描整個堆來確定是否可達, g1通過爲每個region維護一個記憶集合(Rset),內部類似一個反向指針,記錄了內部的對象被哪些外部Region中的對象所引用.(內部互相引用不需要記錄在Rest中,新生代Region之間互相引用不用記錄在Rset中因爲g1的新生代每次gc都是整體gc),只有在和老年代之前發生引用時纔會用到Rset.

Pre Region Table

Rset中通過PRT記錄分區的引用情況,由於Rset需要佔用Region的空間,爲了控制Rset對Region可用空間的影響,PRT採用三種模式記錄Region中的引用情況:

  1. 稀少: 直接記錄Card的索引.
  2. 細粒度: 引用該對象的分區索引.
  3. 粗粒度: 記錄被引用的分區數量.(找到所有的引用就需要掃描整個堆)

CSet

Collection Set, 就是每次回收的Region集合,如果是新生代回收會包含整個新生代的Region,如果是混合回收會包含全部的新生代和回收收益高的老年代Region.

CSet of Young Collection

發生JVM分配對象到Eden區,Eden區滿的時候,發生整個young區域的gc, Eden區域中存活的對象複製到新的Survivor區域,原Survivor區域中的對象根據閾值移到PLAB(新的Survivor區,老年代分區)中.新生代整體回收.

CSet of Mixed Collection

老年代空間佔用超過閾值( -XX:InitiatingHeapOccupancyPercent(默認45%)),g1會啓動一次混合垃圾收集,通過多次回收一部分老年代Region達到控制STW時間在設定的停頓時間範圍內.老年代回收Region是按照回收收益率高低排序.具體次數可以通過參數 -XX:G1MixedGCCountTarget(默認8) 控制.

Barrier

Pre-Write Barrier

在STAB日誌或者緩衝區中記錄喪屍引用的對象

Post-Write Barrier

在Rset log中記錄對象被新引用的信息.

STAB

在併發標記階段,STAB會創建一個對象圖,相當於堆的邏輯快照,在標記的過程中發生的變化一部分會通過Pre-Write Barrier記錄下來,在併發標記的同時會定期檢查處理Pre-Write Barrier的記錄日誌來更新Rset.

Concurrence Refinement Threads

併發優化線程是一個永久活躍的線程,會一直監控Post-Write Barrier的記錄,如果有記錄就會立馬併發處理更新Rset.如果記錄更新過快有可能導致應用線程被暫停來處理此記錄.

轉移失敗的擔保機制 Full GC

轉移失敗(Evacuation Failure)是指當G1無法在堆空間中申請新的分區時,G1便會觸發擔保機制,執行一次STW式的、單線程的Full GC。Full GC會對整堆做標記清除和壓縮,最後將只包含純粹的存活對象。

  1. 從年輕代分區拷貝存活對象時,無法找到可用的空閒分區
  2. 從老年代分區轉移存活對象時,無法找到可用的空閒分區
  3. 分配巨型對象時在老年代無法找到足夠的連續分區
  • Zgc
  1. 暫停不超過10ms
  2. 暫停時間不會應爲內存增大而邊長.
  3. 能管理的內存範圍廣 KB-TB

併發GC

內存標記,複製,遷移操作是併發的.

內存不分代

ZGC依賴NUMA-aware(非均衡存儲器訪問),需要我們的內存支持這種特點

只能工作在64位的操作系統上.

新術語:

着色指針 Colored Pointer

讀屏障Load Barrier

與標記對象的傳統算法相比,ZGC在指針上做標記,在訪問指針時加入Load Barrier(讀屏障),比如當對象正被GC移動,指針上的顏色就會不對,這個屏障就會先把指針更新爲有效地址再返回,也就是,永遠只有單個對象讀取時有概率被減速,而不存在爲了保持應用與GC一致而粗暴整體的Stop The World。

  • OpenJ9

    Memory Management: Memory Allocator and Garbage Collection.

    策略

    1. balanced 單線程標記, 並行清除, 壓縮`.

    2. gencon default

    3. metronome (AIX®, Linux® x86 only)

    4. nogc 不使用gc

    5. optavgpause 犧牲吞吐量優化暫停時間

    6. optthruput 犧牲暫時時間優化吞吐量

    策略選擇: -Xgcpolicy: [policyName]

    Gencon

    啓用:

    -Xgcpolicy:Gencon //j9的默認GC策略

在這裏插入圖片描述

其中Nursery space 又可以分爲 Allocate和Survivor 兩個sub spaces.

Allocate space 是內存分配區域

在Allocate space內存不足時發生local gc: 根據對象的年齡複製到survior 和 tenure space區域中.同時會根據tilting配置重新調整survior和allocate空間的比例.

發佈了56 篇原創文章 · 獲贊 6 · 訪問量 7971
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章