03.22 阿里P8架構專家透析JVM中的垃圾回收機制

jvm的垃圾回收算法,除了我們熟悉的引用計數判斷對象是否活著之外,其他還有那些有意思的東西呢?

總是聽到的年輕代年老代又是啥?

傳說中的YoungGC(MinorGC) 和 FullGC的時機是什麼,又幹了些啥?

I. 對象存活判斷

垃圾回收,回收的都是那些不在使用的對象(也就是沒有存活的對象),因此怎麼判斷對象是否存活,就顯得比較重要了

對這個映像最深刻的就是引用計數方式,一個對象被使用了,計數就+1;不用了,技術就-1;當計數為0的時候,就表示對象沒人用了,簡單粗暴,然而實際的情況中,大都不用這個方式,因為無法解決對象相互循環引用的問題

目前更多的是採用gc root可達性分析,簡單來講就是從一個根節點往下走,走的軌跡上所有的對象,都表示是存活的;也就是說,所有遊離在這個之外的對象,都是需要回收的

那麼什麼是GC ROOT呢 ?

  • 虛擬機棧內引用的對象

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

  • 方法區常量引用的對象

  • 本地方法棧中JNI引用的對象

II. 垃圾回收算法

回收,主要指的是將堆和運行時方法區內沒有存活的對象幹掉;而通常我們所說的垃圾回收,則主要針對的就是堆內的回收

1. 標記-清除算法

簡單理解:根據可達性掃一遍,有用的對象打個標記;剩下來一次大清理,將沒有標記的都ko掉

說明

看書和博文時,常感覺標記,是將需要回收的對象標記出來,但仔細想了下,從實現成本來講,根據可達性分析對象是否存活,順帶的直接將存活的打個標記,比將所有沒存活的上面打上標記要來的簡單,而且這也能算是標記出需要回收的對象

缺點

缺點很明顯,會出現大量的碎片空間

2. 複製算法

將存儲空間一分為二,每次回收就是將這一邊的存活對象搬移到另一邊

缺點

  • 空間少了一半

  • 對於存活時間比較久的對象,需要頻繁的來回搬遷

3. 標記-壓縮算法(或標記-整理算法)

為了節省空間,這個的策略是將所有存活的對象,往某一邊界進行復制,等複製完畢之後,將辯解之外的對象都ko掉

4. 分代收集算法

分代收集,實際來說就是綜合其他算法的優良特性,結合實際應用場景來處理

  • 將存活時間久,佔用空間大的對象,放在老年代

  • 其他的對象可以放在年輕代

也就是說:

老年代中,基本上是老而彌堅的對象,更加適合標記-整理算法,移到一邊之後,由於經常活著,也就避免了頻繁的複製了

新生代中,常是一些朝生夕死的對象,可能用了一次就可以ko,因此可以採用複製算法,標記-清除也是ok的

分代的主要思想就是根據不同的情況,給予不同的策略

III. 簡單說下垃圾收集器

收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現

1. Serial收集

串行收集器,也就是程序跑一會,停下,讓我們的回收線程(只有一個)來實現垃圾回收

新生代、老年代使用串行回收;新生代複製算法、老年代標記-壓縮

2. ParNew收集

上面的多線程版本

新生代並行,老年代串行;新生代複製算法、老年代標記-壓縮

3. Parallel收集

類似ParNew收集器,Parallel收集器更關注系統的吞吐量。可以通過參數來打開自適應調節策略,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量;也可以通過參數控制GC的時間不大於多少毫秒或者比例;如果想學習Java工程化、高性能及分佈式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加Java進階群:582505643,群裡有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家

新生代複製算法、老年代標記-壓縮

4. Parallel Old 收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供

5. CMS收集器

這個是比較常用的,有必要好好了解下

Concurrent Mark Sweep 收集器,是一種以獲取最短回收停頓時間為目標的收集器,核心就是標記-清除算法

a 步驟

  • 初始標記:標記GC Roots能直接關聯到的對象,速度很快,會暫停

  • 併發標記:進行 GC Roots Tracing的過程

  • 重新標記:為了修正併發標記期間,因為程序繼續運作導致標記變動的那一部分對象的標記記錄,一般會長於初始標記時間,遠小於併發標記的時間

  • 併發清除:併發幹掉被回收的問題

初始標記和重新標記的時候,會暫停服務;後面兩個則是併發修改

b. 優缺點

優點:併發收集、低停頓

缺點:產生大量空間碎片、併發階段會降低吞吐量

阿里P8架構專家透析JVM中的垃圾回收機制

6. G1收集器

傳說中是最先進的收集器。。。。

用G1收集器時,Java堆的內存佈局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合

a. 步驟

  • 標記階段:初始標記,會停頓,觸發minorgc

  • Root Region Scanning: 程序運行過程中會回收survivor區(存活到老年代),這一過程必須在minorGC之前完成

  • 併發標記:若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,併發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例);併發執行,可能被minorgc打斷

  • 再標記:再標記階段是用來收集 併發標記階段 產生新的垃圾(併發階段和應用程序一同運行),停頓

  • 複製-整理:併發幹掉死亡對象,G1將回收區域的存活對象拷貝到新區域

IV. GC分析

這個日誌主要針對的是CMS收集器的分析,因為我接觸的應用,服務器上就是選擇的這個…

看一張神奇的圖

阿里P8架構專家透析JVM中的垃圾回收機制

內存分配和回收策略

a. 對象優先在Eden分配

大多數場景下,對象在新生代Eden區分配,當Eden去沒有足夠的空間進行分配時,虛擬機發起一次 Minor GC

  • 新生代MinorGC : 發生在新生代的垃圾收集動作,因為java對象大多都具備朝生夕滅的特性是,所以一般MinorGC非常頻繁,一般回收速度也很快

  • 老年代MajorGC(FullGC) : 發生在老年代的GC,通常就伴隨至少一次的MinorGC(非絕對),一般較慢,是MinorGC的十倍以上

b. 大對象直接進入老年代

需要大量連續內存空間的Java對象,通常是數組,同構 -XX:PretenuresizeThreshold 參數,來設置大對象的閥值,超過這個閥值的直接分配在年老代,避免在Eden區及兩個Survivor區之間發生大量的內存複製

c. 長期存活的對象將進入老年代

既然虛擬機採用分代收集的思想來管理內存,在回收時,就必須能識別哪些對象應放在新生代,那些對象應放在老年代中

每個對象都有個Age的計數器,對象在Eden出生並經過第一次MinorGC後仍存在,且可以被Survivor容納的話,會被移動到Survivor空間中,並設置Age為1

對象在Survivor區沒多經過一次MinorGC,則age+1

當age超過閥值(默認15),就會晉升到老年代

閥值可以通過 -XX:MaxTenuringThreshold來設置

d. 動態對象年齡判定

如果在Survivor空間中相同年齡所有對象的大小的總和,大於Survivor空間的一半,則年齡大於或等於該年齡的對象就可以進入老年代,無需等Age達到閾值如果想學習Java工程化、高性能及分佈式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加Java進階群:582505643,群裡有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家

e. 空間分配擔保

在發生MinorGC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果成立,則Minor GC可以確保總是安全的;

否則,查看 HandlePromotionFailure參數,是否允許擔保失敗

若允許,則繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若大於,則嘗試MinorGC

否則進行FullGC

V. 小結

1. 怎麼判斷對象是否存活

兩種方式,引用計數和可達性分析

引用計數: 循環依賴問題,沒啥用

可達性:從gc roots出發,可達的都是存活的

2. 幾種回收算法對比

<table><thead>算法簡述缺點
/<thead><tbody>標記-清除標記對象,統一清楚可回收對象大量碎片複製算法內存一分為二,將存活的移動到另一邊存活久的對象,頻繁複制;空間變小標記-整理存活對象往一邊界拷貝,邊界外的都幹掉對於生命週期特別短的不太合適分代年輕代 + 年老代,不同代選用不同算法-/<tbody>/<table>

3. CMS和G1階段對比

cms主要區分四個步驟:

  • 標記:停頓

  • 併發標記

  • 重新標記:停頓,重新處理併發過程中新標記的對象

  • 併發清除:併發回收

g1,從結構上而言,劃分為一個個獨立區域(region),採用標記-整理算法,避免碎皮空間

4. 簡述內存分配和回收

基於CMS進行說明

  • 優先分配edge區(不夠則觸發gc)

  • 大對象,分配在old區

  • 存活時間久的塞入old區

  • 動態時間判斷(某個age對象總和大於Survivor一半,則塞入old區)

  • 分配擔保(進入old區,但是old區空間不夠的策略,決定是否觸發gc)

阿里P8架構專家透析JVM中的垃圾回收機制

阿里P8架構專家透析JVM中的垃圾回收機制


分享到:


相關文章: