JVM 中的垃圾回收

萌新J發表於2021-03-10

說到JVM,總是繞不開垃圾回收,因為其是JVM的核心之一,因為物件的建立是非常頻繁的,想要提高程式的執行效率,擁有一個高效的垃圾回收機制是必不可少的。

首先要明確,因為物件的建立只在堆中,所以垃圾回收主要發生在堆中,但是垃圾回收並不只是回收物件,也會回收一些廢棄的常量和類物件。所以垃圾回收作用的位置是在堆和方法區上的。

垃圾的定位和執行

定位

當一個物件沒有被引用時就可以被回收,但是問題是如何判斷一個物件沒有被引用呢?目前確定一個物件是否被引用有兩種方法。

1、引用計數法。為物件建立一個計數器,當這個物件被引用時計數器加1,失去引用時減1。在垃圾回收時會判斷計數器的值是否為0,如果為0說明可以回收,不為0不可以。

2、可達性分析。從 GC Roots 開始向下搜尋,將所有結果的方法的物件全部記錄下來,如果當前物件不在這個記錄中,就是可以被回收的。(HotSpot 使用)

 

GC Roots 包括以下元素:

1、虛擬機器棧引用的物件。如各個執行緒被呼叫的方法中使用的引數、區域性變數等。

2、本地方法棧內本地方法引用的物件。

3、方法區中類屬性引用的物件。

4、方法區常量引用的物件。如字串常量池中引用的物件(1.7 開始)

5、同步鎖持有的物件。

6、JVM 內部的一些引用(基本資料型別對應的 Class 物件,一些常駐的異常物件)

7、在回收區域性物件時,其他位置的物件也可以作為 "臨時 GC Roots" 。比如只回收新生代的物件,那麼老年代和方法區的物件 可以作為 GC Roots。

 

比較如果單從效率來看,引用計數法是好於可達性分析的,因為引用計數法只要進行一次判斷就可以了。但是 HotSpot 使用的卻是第二種,這是因為引用計數法存在回收的 bug。加入有兩個物件,其各自的屬性值都是對方的物件,那麼這兩個的引用都不為0,但是他們形成了一個環狀引用,並沒有被外部所引用,所以是應該被回收的,但是以為引用計數法的規則又不會被回收,這就造成了記憶體洩漏,為 OOM 埋下了隱患。

 

回收方法 Finalize

過程:一個物件被回收前,至少會被進行 "兩次標記"。當一個物件進行不可達時,會對其進行第一次標記,標記為 "可回收狀態"。隨後會檢查其是否重寫了 finalize 方法(Object 類的方法,所以每個物件都有),如果沒有重寫,那麼直接將其標記為 "沒有必要執行",然後加入待回收佇列,隨後等待回收。如果重寫了 finalize 方法,那麼就會讓一個優先順序較低的執行緒去執行這個方法(之所以優先順序較低,是防止重寫的方法中出現死迴圈影響程式執行,所以 finalize 方法在回收前並不一定會執行完),在執行完後也會將其加入待回收佇列等待回收。

而如果在執行 finalize 方法時,讓這個物件再次被 GC Roots 引用,那麼這個物件在第二次標記時,就會被移出待回收佇列,隨後當這個物件再一次進行不可達狀態時,這時就會直接進入待回收佇列,而不會再次執行 finalize 方法。

 

注意

1、finalize 方法是在物件回收前執行的方法,所以可以在這個方法中進行一些資源的釋放、清理工作。所以永遠不要去主動呼叫一個物件的 finalize 方法。

2、finalize 方法重寫需慎重,不然會影響程式的執行效率。

3、如果想要主動回收某個類,可以使用 System.gc() 通知 GC 執行 full gc,但是隻是通知,GC 會根據當前情況來判斷是否會執行。

 

安全點和安全區域

安全點

程式執行時並非所有地方都可以停頓下來進行 GC,只有在特定的位置才能停頓下來開始 GC,這些位置稱為 "安全點"。安全點一般選取一些執行時間長的操作,保證執行時的效能受到的影響很小。

 

在GC 發生時,如何保證所有的執行緒都跑到最近的安全點停下來呢?

1、搶先式中斷(未使用)。首先中斷所有執行緒,然後某個執行緒不在安全點上,就再啟動讓其執行到安全點再中斷。

2、主動式中斷。設定一箇中斷標誌,當執行緒執行到安全點就開始輪詢這個標誌,然後判斷中斷狀態是否為真,如果為真就將自己中斷掛起。

 

安全區域

當執行緒處於 "未執行" 狀態時發生 GC ,那麼因為這個執行緒沒有處於安全點,所以 GC 就會等這個執行緒執行到安全點才會進行 GC ,這樣是十分耗時的,所以引入了安全區域的概念。安全區域就是一段引用關係不會發生變化的區域。比如呼叫 sleep()、wait() 方法。

 

實際執行

首先會排除處於安全區域的執行緒,然後判斷剩下的執行緒是否都處於安全點,在發出 GC 通知後,會將中斷狀態設為 true,而執行到安全點的執行緒就會輪詢判斷、掛起。最後當檢測所有的執行緒都進入安全點後就會執行 GC。

 

垃圾回收過程

堆的結構

首先要知道,堆在不同時期的結構是不一樣的,在 G1 垃圾回收器之前,堆是下圖的結構。由於 G1 垃圾回收器比較複雜,同時回收過程會用到之前的基礎,所以先以 G1 之前為例來看。

堆主要分為新生代和老年代,新生代,也就是圖中的 Young 區和 Old 區。而 Permanent 對應的是永久代,雖然其是方法區的實現,但是從邏輯上來看也屬於堆。

在新生代又分為 Eden 區和兩個 Survivor 區。比例預設為 8:1:1,可以通過 -XX:SurvivorRatio 來修改這個比例。新生代用於存放新建立並且佔空間較小的物件。老年代用於存放存活達到一定時間或佔空間較大的物件。

 

回收型別

1、部分回收:

  1)Minor GC(Young GC):發生在新生代的 GC,當 Eden 區滿後就會觸發 Minor GC。只收集新生代的垃圾。

  2)Major GC:發生在老年代的 GC,只收集老年代的垃圾。目前只有 CMS 支援單獨回收老年代的行為。

  3)Mix GC:混合回收,收集新生代和老年代的垃圾。

2、整堆收集(Full GC)。收集整個 java 堆和方法區的垃圾。方法區收集的物件是未引用的常量以及類。之前的文章也說過,類被回收的條件非常苛刻,必須滿足下面三個條件:

  1)該類對應的物件全部被回收

  2)該類對應的 Class物件無法被訪問‘

  3)載入該類的類載入器被回收

雖然分為這麼多種類,但是一般主要執行的是 Minor GC 和 Full GC,Major GC 一般情況下都是伴隨著 Full GC 的執行,所以我們一般只考慮這兩個 GC。Full GC 消耗的時間是 Minor GC 的十倍以上,並且 GC 會造成 STW(stop the world,也就是工作執行緒全部暫停)所以應該避免 Full GC。

 

執行過程

在堆剛初始化時,新建的第一個物件會存入新生代的 Eden 區,如果物件過大那麼會進行一次 Minor GC,隨後放入新生代兩個 Survivor 區的其中一箇中,然後還是不夠大,那麼直接放入老年代,如果老年代空間再不夠存放那麼直接執行 full gc,所以並不是所有的物件都會在新生代分配物件。當物件存入後,隨著物件的越來越多,當 Eden 區滿了後,就會觸發 Minor GC,將Eden 區以及其中一個存放 Survivor 區的物件進行標記、回收,然後將存活的物件全部存入另一個空的 Survivor 區中。而每次執行一次 Minor GC,存活下來的物件都會將其物件頭的GC年齡部分+1,當達到一定年齡後,這個物件就會隨著下此GC晉升到老年代(CMS預設為6,其他預設都是15)。

空間分配擔保

由於新生代的晉升機制,使得每一次 Monir GC 晉升到老年代的物件都可能會導致老年代空間不足從而發生 Full GC,所以 JVM 維護了一個空間分配擔保機制。避免發生 Minor GC 之後又發生 Full GC,最大程度地影響了程式的執行效率。

在發生 Minor GC 之前,虛擬機器會檢查老年代最大可用的連續空間是否大於新生代所有物件的總空間。

1、大於。說明此次 Minor GC 是安全的,直接執行 Minor GC。

2、小於。檢視設定的 HandlePromotionFailure,這個值表示是否允許擔保失敗,如果是 true,那麼會繼續檢查老年代最大可用的連續記憶體是否大於歷代晉升到老年代的物件平均大小,

                                          如果大於,則嘗試執行 Minor GC,此次GC 是存在風險的,因為可能晉升的物件會造成 Full GC;

                                     如果小於或者這個引數值為 false,那麼直接執行 Full GC。

上面的規則是 JDK6 之前的,在 JDK6 開始, HandlePromotionFailure 不會再作為影響執行的因素了。而是直接檢查老年代可用的最大連續記憶體是否大於歷代晉升的物件平均值,如果大於執行 Minor GC,否則執行 Full GC。

 

注意

1、並不是所有的物件都需要等到 GC 達到指定的年齡後才能晉升。當某個年齡的物件佔空間達到 survivor 區的一半時,那麼 survivor 區中所有大於這個年齡的物件都在在下一次 GC 時晉升到老年代。

2、並不是 OOM 都會觸發垃圾回收器。對於超大物件,大小直接超過堆的總大小,JVM 會判斷這個物件是垃圾回收無法解決的,直接丟擲 OOM。

 

觸發時機

Minor GC:

1、Eden 區空間無法存放新物件的。

2、Full GC 會觸發 Minor GC。

Full GC:

1、空間分配擔保,老年代可用的連續記憶體小於歷代晉升的物件平均值。

2、分配給老年代的物件大於老年代可用的最大連續記憶體

 

垃圾回收演算法

基礎演算法:

1、標記-清除:對不需要清理的物件標記、清除,不作任何額外操作。

缺點:會產生記憶體碎片,使得 GC與 OOM 更容易發生。

優點:1、執行快。2、記憶體利用率高

注意:這裡的清理並不是直接刪除物件,而是將物件的地址儲存到空閒列表中,在 物件的建立和分配 中說過對於物件儲存不規整的堆,分配空間是需要維護一個空閒列表來記錄可用的位置,而標記-清除就是會造成物件記憶體不規整的場景,所以分配空間就是通過空閒列表,而一個物件被標為可回收後就可以直接加入空閒列表,然後下一次分配時就可以直接覆蓋原有的物件。

 

2、複製演算法:複製演算法一般是使用兩個相同大小的區域,兩塊區域中永遠有一個永遠保持清空狀態,當垃圾回收時,將存活下來的物件移入空的區域中,然後清空剩下的所有物件,等待下一次GC時再反過來。前面說到的新生代的 Minor GC 採用的就是複製演算法。

優先:1、效率高。2、實現簡單。

缺點:1、記憶體利用率不足。2、在物件存活率高的場景中使用會影響效率。(這也是為什麼老年代使用的不是複製演算法)

 

3、標記-整理演算法:將不需要的物件標記,然後移到一起,再刪除剩下的物件。HotSpot 老年代使用的演算法。

優點:1、沒有產生記憶體碎片,避免了更頻繁的 GC與 OOM。2、記憶體利用率高

缺點:1、每次 GC 都需要去改變物件地址,效率較低。

 

組合演算法

組合演算法其底層還是使用前面三種基礎演算法,只不過在此基礎上做了一些組合擴充。

1、分代演算法:根據物件的存活週期將物件劃分為不同的部分,每個部分使用每個部分的回收演算法。這也是 HotSpot 使用的演算法。

2、增量收集演算法:將GC過程分為多段,每次只執行一部分。優點是使用者體驗會更好。缺點是切換會產生上下文切換的成本,造成系統的吞吐量( 使用者執行緒時間 / (使用者執行緒時間+GC執行緒STW時間) )降低。

3、分割槽演算法:將堆拆分成多個小分割槽,每次回收只需要對每塊小分割槽內的物件進行操作。提升了執行效率。

 

垃圾回收器

垃圾回收器是垃圾回收過程的核心之一,隨著 JDK 版本的迭代,垃圾回收器也經歷了多次迭代變化,新出的垃圾回收器越來越強大,但是不能說新出的垃圾回收器就一定強於之前的回收器。其執行效率的高低迴收要看使用場景。

分類

按執行緒數:

序列垃圾回收器:單執行緒執行GC

並行垃圾回收器:多執行緒執行GC

按工作模式:

併發式垃圾回收器:和使用者執行緒交替執行,實現的有 CMS、G1

獨佔式垃圾回收器:在GC時會停止所有的使用者執行緒,也就是STW

按碎片處理:

壓縮式垃圾回收器:會進行記憶體整理,不會產生記憶體碎片。

非壓縮式垃圾回收器:不會進行記憶體整理,使用空閒列表來完成物件的空間分配。會產生記憶體碎片。

按工作的記憶體區間:

年輕代垃圾回收器:回收新生代。

老年代垃圾回收器:回收老年代。

 

吞吐量與低延時關係

吞吐量是衡量使用者執行緒的執行效率的,其計算公式是 " 使用者執行緒執行時間 / (使用者執行緒執行時間+GC造成的 STW時間) "。吞吐量越高的程式執行效率越高。

低延時是指使用者一次GC時造成的 STW 時間比較短,其主要是用於提高使用者的體驗,上面也說過 "增量收集演算法",就是將 GC 分為多次,使得單次的 GC STW 時間較短,使得使用者體驗更好,但是切換引起的上下文切換成本會使 吞吐量降低。

所以,吞吐量和低延時是魚和熊掌,不可兼得,要想吞吐量高就必須犧牲低延時;而如果想要低延時,就必須犧牲一定的吞吐量。

 

實現

1、Serial 收集器。

新生代垃圾回收器,單執行緒序列。在單核CPU下執行效率高。

 

2、ParNew 收集器。

新生代,並行垃圾回收器。是 Serial 的多執行緒版本。在 JDK後面版本被孤立了(沒有與它搭配的老年代回收器了)

 

3、Parallel Scavenge 收集器。

新生代,並行收集器。與 ParNew 的區別是可以已有更高的吞吐量,高效利用 CPU。

 

4、Serial Old 收集器。

老年代,單執行緒序列。是 Serial 的老年代版本。

 

5、Parallel Old 收集器。

老年代,並行收集器。追求高吞吐量。是Parallel Scavenge 的老年代版本。

 

小結:上面五種收集器實現都比較簡單,都是獨佔式垃圾回收器,並行垃圾回收器,新生代的三個使用的都是複製演算法,老年代的兩個都是使用標記-整理演算法。在執行時都會造成 STW。而下面的三種垃圾回收器則可以與使用者執行緒併發執行,兼顧了低延時和吞吐量。

 

CMS 垃圾回收器

CMS 是老年代的垃圾回收器,使用的是標記-清除演算法。他不是和前幾個一樣直到物件滿後才會進行GC,而是達到某個閥值就會開始垃圾回收。 

過程:

1、初始標記。標記主方法直接關聯的物件,此過程會造成 STW,但消耗的時間較短。

2、併發標記。標記所有不可達物件,這一步是與使用者執行緒併發執行的,不會造成 STW。 但是會造成錯標漏標,比如剛剛掃描完的物件在使用者執行緒的作用下變成不可達狀態,或者某個物件在掃描標記為可回收後又被使用者執行緒引用。所以在第二步之後還需要進行進一步的檢查。

3、重新標記。修改剛才錯標的情況。但是對於漏標的(掃描時是可達,隨後變成不可達)物件不會修正。這一步也會造成 STW。但是以為錯標的物件在所有需要回收的物件中佔比較小,所以執行的效率還是較高的,只比第一步初始標記消耗的時間略長。

4、併發清除。併發清除所有的不可達物件,不會發生 STW。

CMS 在與使用者執行緒併發時可能會造成某些物件漏標導致物件空間不足,這時因為 CMS 還在工作,無法再回收,所以,當 CMS 在併發時出現記憶體空間不足時,首先會丟擲 "Concurrent Mode Failure" 異常,然後啟用備用的垃圾回收器:Serial Old 進行回收。

 

為什麼 CMS 採用的是標記-清理演算法

CMS 與使用者執行緒是併發執行的,如果採用標記-整理演算法,那麼就可能會在與使用者併發執行時整理物件,這時物件的地址就會改變,影響使用者執行緒的執行。導致執行緒崩潰。

 

小結:

優點:CMS 是 JDK 首款併發的併發式垃圾回收器。它的出現使得垃圾回收過程與使用者執行緒可以同時執行,在提高程式吞吐量的同時也降低了延時。

缺點:1、因為降低了延時,所以吞吐量是會有所降低的。2、因為是標記=清除演算法,所以會導致 full gc 與 OOM 發生的概率更高。

 

G1 垃圾回收器(JDK9預設垃圾回收器)

G1 是在 CMS 的思路改造的。但其也是具有劃時代的意義的。其打破了傳統的模式,引入了分割槽的概念。將整個的堆劃分為多個 region 區域。每塊 region 區種類可以是 eden、survivor、old、Humongous(專門儲存大物件,一般的大小是一般 region 的。15倍,大物件優先選擇 H 區儲存,當一個H區儲存不下就會區尋找連續的H區存放)。新生代物件還是優先存入 eden 和 survivor 區中,到達年齡後晉升到老年代的 region 區,實現邏輯分代,物理不分代。

特點

1、由於將堆劃分為多個 region區,所以在回收時只需要將其中存活的物件移入相鄰空閒的 region區,再回收就可以了,效率大大提高,也不會產生記憶體碎片,同時也不會使利用率降低。微觀來看不管是新生代回收老年代都是複製演算法。巨集觀來看是標記-整理演算法。

2、可預測的停頓時間模型。讓使用者明確指定一個長度為 M 毫秒的時間內,消耗在垃圾收集的時間不得超過 N 毫秒。G1 會根據各個 region 區回收時間維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 region。比如指定時間是 20ms,那麼後臺就會從優先列表裡選擇總耗時低於 20ms 的優先順序高的若干塊 region 區進行回收。可以通過  -XX:MaxGCPauseMills 設定,G1 會盡力實現,但不能保證一定可以實現。

3、Remebered Set機制。在每個 region 區都維護一個 Remebered Set(RSet)表,儲存當前 region 中物件被其他 region 區物件的引用詳情。這樣在GC時就可以將所有的 RSet 表資料加入 GC  Roots根節點的列舉範圍中,避免了全域性掃描,同時也不會遺漏。而在記錄時會先將記錄記載到中間表 dirty card queue 中,等到 GC 時。這樣是因為 Rest 表更新是需要執行緒同步的,為了避免頻繁的同步影響程式效能,所以使用 dirty card queue 作為中間表。

 

過程

1、年輕代GC(Minor GC)。當所有的 eden 型別的 region 區總容量滿了後,對 eden 和 survivor 區進行回收,晉升的放入老年代,其餘存活的尋找有空閒位置的 survivor 區儲存。在標識時會使用 RSet 和 dirty card queue 來記錄協助GC。

2、併發標記(老年代標記)。當老年代記憶體使用佔比達到一定值(預設45%)後,開始進行老年代併發標記過程。這一過程主要是對各個區域進行掃描,計算要回收的物件活性(GC回收物件的比例)並排序,清理完全是垃圾的 region 區。

  1)首先執行初始標記(STW)。標記從根節點直接可達的物件。

  2)跟區域掃描。掃描 Survivor 區直接可達的老年代區域物件,這一步必須在 Young GC 之前完成。

  3)併發標記。在使用者執行緒併發執行,標記整個堆中的存活物件,如果某個 region 區全部都是垃圾,那麼就會立刻回收該區域。然後計算每塊區域的物件活性(存活物件比例)

  4)再次標記。因為上一步是併發的,所以這一步是解決漏標的物件,但是使用了比 CMS 更快的初始快照演算法。

  5)獨佔清理(STW)。計算各個區域的存活物件和 GC回收的比例,並進行排序。

  6)併發清理。識別並清理完全空閒的區域。

3、混合回收。在老年代物件佔用區域達到一定比例,會觸發混合回收,但不是 full gc。混合回收會回收年輕代和一部分老年代,預設會將老年代回收分八次回收,回收的順序就是按上一步生成的排序從高到低來進行。但是並一定就是八次,當某個區域垃圾佔比小於   

-XX:G1MixedGCLiveThresholdPercent(預設65%),那麼就不會參與回收。同時還有一個引數 -XX:G1HeapWastePercent(預設10%)設定可浪費的比例,也就是如果要回收的總物件佔比小於堆總大小的比例小於這個值,那麼就不會進行回收。

 

優勢

功能強大,兼顧吞吐量和低延時,沒有記憶體碎片產生,可以自定義停頓時間。

劣勢

需要更高的配置才能啟用,在記憶體小的場景執行效率並沒有那麼高。

 

ZGC

是 G1 的升級版,在 G1 分割槽的基礎上,不設分代,使用讀屏障、染色指標和記憶體多重對映等技術來實現可併發的標記-壓縮演算法的。在吞吐量影響不大的前提下,把垃圾收集的停頓時間限制在十毫秒以內的低延遲。工作的四個階段:併發標記-併發預備重分配-併發重分配-併發重對映 等。除了初始標記是STW其他都是並行的。因為其暫時還不穩定,所以還不是主流的垃圾回收器。

 

G1 與 CMS 的對比

1、G1 不會產生垃圾碎片,而CMS 因為是標記-清除演算法,會產生碎片,提高 full gc 與OOM 的概率。

2、在條件足夠的場景下,G1 的效能要強於 CMS,而 CMS 在有限記憶體下效率是高於 G1 的。

 

總結

對於上面提到的這些垃圾回收器,不能說 G1 ,CMS 的執行效率就一定比 ParNew、甚至 Serial 要高,收集器的使用要結合場景,如果是單核的場景,那麼 Serial + Serial Old 的效率要高於其他任意一個組合,而如果是多核但記憶體不夠,那麼 CMS的效率又會比 G1 要高,只有當條件允許時,G1 的效率才是最好的。

 

不同版本下垃圾回收器的搭配

 

相關引數

常用

-Xss512k設定單個棧容量512k,一般是512k-1024k;

-Xms2g:初始化推大小為 2g,預設是實體記憶體的 1/64;

-Xmx2g:堆最大記憶體為 2g,預設為實體記憶體的 1/4。

-Xmn125k:新生代大小125k

-XX:NewRatio=4:設定年輕的和老年代的記憶體比例為 1:4;預設是2。

-XX:SurvivorRatio=8:設定新生代 Eden 和 Survivor 比例為 8:1:1;預設是8

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;

-XX:+PrintGC:開啟列印 gc 資訊(列印的內容比較簡單);

-XX:+PrintGCDetails:列印 gc 詳細資訊。

-XX:+PrintGCTimeStamps:輸出GC的時間戳(以基準時間的形式)

-XX:+PrintGCDateStamps:輸出GC的時間戳(以日期的形式)

-XX:+PrintHeapAtGC:在進行GC的前後列印出堆的資訊

-Xloggc:../logs/gc.log:日誌檔案的輸出路徑

-XX:+PrintFlagsInitial:檢視所有引數的預設初始值

-XX:+PrintFlagsFinal:檢視所有引數的最終值(可能存在修改,不再是初始值)

-XX:MaxTenuringThreshold:設定新生代晉升年齡閥值

-XX:+PrintGCDetails:輸出詳細的GC處理日誌

-XX:+DoEscapeAnalysis顯式開啟逃逸分析

-XX:+PrintEscapeAnalysis檢視逃逸分析的篩選結果

-XX:EliminateAllocations開啟了標量替換(預設開啟),允許將物件打散分配在棧上

-XX:PermSize= 設定永久代初始容量

-XX:MaxPermSize= 設定永久代最大容量

-XX:MetaspaceSize= 設定元空間初始大小(預設21m)

-XX:MaxMetaspaceSize= 設定元空間最大空間

-XX:MaxDirectMemorySize=設定直接記憶體的大小,預設和堆最大值,也就是-Xmx大小一致。

編譯器

-XX:-UseCounterDecay :方法呼叫計數器關閉熱度衰減

-XX:CounterHalfLifeTime :設定半衰週期的時間

-Xint完全使用直譯器模式去執行程式

-Xcomp完全採用即時編譯器去執行程式。如果程式出現問題,直譯器會介入執行。

-Xmixed採用直譯器+即時編譯器的混合模式共同執行程式。

 字串常量池

-XX:StringTableSize= 設定字串常量池的 StringTable 陣列長度

-XX:+PrintStringTableStatistics  開啟列印StringTable統計資訊

 OOM

-XX:+HeapDumpOnOutOfMemoryErrorOOM時自動生成Heapdump檔案

垃圾回收器

-XX:+PrintComandLineFlags檢視命令列相關引數(包含使用的垃圾收集器)

使用命令列:jinfo -flag 相關垃圾回收器引數 程式ID

-XX:+UseSerialGC:表明新生代使用Serial GC ,同時老年代使用Serial Old GC

ParNew

-XX:+UseParNewGC:標明新生代使用ParNew GC 

-XX:ParallelGCThreads設定執行緒數量,預設開啟和CPU資料相同的執行緒數(在併發量要求小的專案中執行緒數多會增加執行緒切換的成本;在併發量要求大的專案中執行緒數少會導致效率不高)

Parallel Scavenge

-XX:+UseParallelGC:表明新生代使用Parallel GC

-XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC

 *  說明:二者可以相互啟用

-XX:ParallelGCThreads設定執行緒數量。預設CPU數量小於8時等於8;CPU數量大於8等於3+[5 * CPU_Count]/8

-XX:MaxGCPauseMillis設定垃圾回收器最大停頓時間(也就是延遲時間,單次STW時間),該引數使用需謹慎

-XXGCTimeRatio垃圾收集時間佔總時間的比例,用於衡量吞吐量的大小,與前一個引數相矛盾,停頓時間越短,垃圾收集的時間比例就越高。比例公式是1/(n+1),預設是99,也就是垃圾收集時間佔比就是1/100。

-XX+UseAdaptiveSizePolicy開啟Parallel Scavenge的自適應調節策略。可以通過指定最大堆空間,吞吐量,最大停頓時間(也就是上面兩個引數)來讓虛擬機器自己完成調優工作。

CMS:

-XX:+UseConcMarkSweepGC老年代使用CMS垃圾回收器,開啟後會自動開啟-XX:+UseParNewGC開啟。即:ParNew(新生代)+CMS(老年代)+Serial Old(備用)

-XX:CMSlnitiatingOccupanyFraction設定堆記憶體使用率的閥值,一旦達到該閥值,便開始進行回收。jdk6之前預設68,6之後預設92。記憶體增長緩慢可以設定高一些,增長快可以設定低一些。

-XX+UseCMSCompactAtFullCollection:開啟在執行完full gc後對記憶體空間進行整理,避免記憶體碎片產生。

-XX:CMSFullGCsBeforeCompaction:搭配上面的引數,設定在執行完多少次full gc後進行碎片整理。

-XX:ParallelCMSThreads:設定CMS的執行緒數量。預設是(ParallelGCThreads+3)/4

G1:

-XX:+UseG1GC:使用G1收集器

-XX:G1HeapRegionSize:設定每個Region的大小。值是2的冪,範圍是1MB32MB之間。預設是堆記憶體的1/2000。

-XX:MaxGCPauseMills:設定期望達到的最大GC停頓時間指標(JVM會盡力實現,但不保證達到)。預設值是200ms。

-XX:ParallelGCThread:設定STW時GC執行緒數的值,預設是8。

-XX:ConcGCThreads:設定併發標記的執行緒數。一般設定為上面STW GC執行緒數的1/4左右。

-XX:InitiatingHeapOccupancyPercent:設定觸發併發GC週期的Java堆佔用閥值。超過此值,就觸發GC,預設值是45。

相關文章