JVM 中的垃圾回收

說到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。

Tags: