天天看點

垃圾回收機制(GC)1. 哪些“垃圾”需要回收?2. 那些對象被判斷為垃圾?3. 什麼時候回收垃圾4. ★如何進行垃圾回收(垃圾回收算法)?5. TLAB6.GC 相關參數總結

1. 哪些“垃圾”需要回收?

JVM結構五大區中,有三個是不需要進行垃圾回收的:程式計數器、JVM棧、本地方法棧。因為它們的生命周期是和線程同步的,随着線程的銷毀,它們占用的記憶體會自動釋放,是以隻有方法區和堆需要進行GC。具體到哪些對象的話,簡單概況一句話:如果一個對象不能到達GC Roots對象的時候,那麼它可以被回收。通俗解釋一下就是說,如果一個對象,已經沒有什麼作用了,就可以被當廢棄物被回收了。

2. 那些對象被判斷為垃圾?

根據一個經典的引用計數算法,每個對象添加一個引用計數器,每被引用一次,計數器加1,失去引用,計數器減1,當計數器在一段時間内保持為0時,該對象就認為是可以被回收得了。但是,這個算法有明顯的缺陷:當兩個對象互相引用,但是二者已經沒有作用時,按照正常,應該對其進行垃圾回收,但是其互相引用,又不符合垃圾回收的條件,是以無法完美處理這塊記憶體清理,是以Sun的JVM并沒有采用引用計數算法來進行垃圾回收。而是采用一個叫:根搜尋算法,如下圖:

垃圾回收機制(GC)1. 哪些“垃圾”需要回收?2. 那些對象被判斷為垃圾?3. 什麼時候回收垃圾4. ★如何進行垃圾回收(垃圾回收算法)?5. TLAB6.GC 相關參數總結

基本思想就是:從一個叫GC Roots的對象開始,向下搜尋,如果一個對象不能到達GC Roots對象的時候,說明它已經不再被引用,即可被進行垃圾回收(對于用可達性分析法搜尋不到的對象,GC并不一定會回收該對象。要完全回收一個對象,至少需要經過兩次标記的過程。

要宣布一個對象死亡至少要經曆兩次标記過程:如果通過可達性分析沒有與GC Roots相連接配接的引用鍊,會被第一次标記和進行一次篩選。篩選條件是此對象是否有必要執行finalize()方法,當對象沒有覆寫finalize()方法,或者finalize()方法已經被虛拟機調用過。虛拟機将這兩種情況都視為“沒有必要執行”。如果一個對象有必要執行finalize()方法,會被放在F-Queue隊列,并在稍後一個由虛拟機自己建立的、低優先級的Finalizer線程去執行它,finalize()方法是對象最後一次逃脫死亡機會。稍後GC将對F-Queue中的對象進行第二次小規模标記,如果對象在第二次标記前沒能拯救自己就要被回收。拯救辦法是隻要與引用鍊上任何一個對象建立關聯即可。如上圖中的Object5、Object6、Object7,雖然它們3個依然可能互相引用,但是總體來說,它們已經沒有作用了,這樣就解決了引用計數算法無法解決的問題。

補充引用的概念:JDK 1.2之後,對引用進行了擴充,引入了強、軟、若、虛四種引用,被标記為這四種引用的對象,在GC時分别有不同的意義:

    a>強引用(Strong Reference).就是為剛被new出來的對象所加的引用,它的特點就是,永遠不會被回收。

    b>軟引用(Soft Reference).聲明為軟引用的類,是可被回收的對象,如果JVM記憶體并不緊張,這類對象可以不被回收,如果記憶體緊張,則會被回收。此處有一個問題,既然被引用為軟引用的對象可以回收,為什麼不去回收呢?其實我們知道,Java中是存在緩存機制的,就拿字面量緩存來說,有些時候,緩存的對象就是目前可有可無的,隻是留在記憶體中如果還有需要,則不需要重新配置設定記憶體即可使用,是以,這些對象即可被引用為軟引用,友善使用,提高程式性能。

    c>弱引用(Weak Reference).弱引用的對象就是一定需要進行垃圾回收的,不管記憶體是否緊張,當進行GC時,标記為弱引用的對象一定會被清理回收。

    d>虛引用(Phantom Reference).虛引用弱的可以忽略不計,JVM完全不會在乎虛引用,其唯一作用就是做一些跟蹤記錄,輔助finalize函數的使用。

最後總結,什麼樣的類需要回收呢?無用的類,何為無用的類?需滿足如下要求:

   1>該類的所有執行個體對象都已經被回收。

   2>加載該類的ClassLoader已經被回收。

   3>該類對應的反射類java.lang.Class對象沒有被任何地方引用。

3. 什麼時候回收垃圾

3.1Minor GC

從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC。這一定義既清晰又易于了解。但是,當發生Minor GC事件的時候:

  1. 當 JVM 無法為一個新的對象配置設定空間時會觸發 Minor GC,比如當 Eden 區滿了。是以配置設定率越高,越頻繁執行 Minor GC。
  2. 記憶體池被填滿的時候,其中的内容全部會被複制,指針會從0開始跟蹤空閑記憶體。Eden 和 Survivor 區進行了标記和複制操作,取代了經典的标記、掃描、壓縮、清理操作。是以 Eden 和 Survivor 區不存在記憶體碎片。寫指針總是停留在所使用記憶體池的頂部。
  3. 執行 Minor GC 操作時,不會影響到永久代。從永久代到年輕代的引用被當成 GC roots,從年輕代到永久代的引用在标記階段被直接忽略掉。
  4. 質疑正常的認知,所有的 Minor GC 都會觸發“全世界的暫停(stop-the-world)”,停止應用程式的線程。對于大部分應用程式,停頓導緻的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認為是垃圾,永遠也不會被複制到 Survivor 區或者老年代空間。如果正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間将會長很多。

是以 Minor GC 的情況就相當清楚了——每次 Minor GC 會清理年輕代的記憶體。

3.2 Major GC vs Full GC

大家應該注意到,目前,這些術語無論是在 JVM 規範還是在垃圾收集研究論文中都沒有正式的定義。但是我們一看就知道這些在我們已經知道的基礎之上做出的定義是正确的,Minor GC 清理年輕帶記憶體應該被設計得簡單:

  • Major GC 是清理老年代。
  • Full GC 是清理整個堆空間—包括年輕代和老年代。

很不幸,實際上它還有點複雜且令人困惑。首先,許多 Major GC 是由 Minor GC 觸發的,是以很多情況下将這兩種 GC 分離是不太可能的。另一方面,許多現代垃圾收集機制會清理部分永久代空間,是以使用“cleaning”一詞隻是部分正确。

這使得我們不用去關心到底是叫 Major GC 還是 Full GC,大家應該關注目前的 GC 是否停止了所有應用程式的線程,還是能夠并發的處理而不用停掉應用程式的線程。

Full GC觸發條件:

(1)調用System.gc時,系統建議執行Full GC,但是不必然執行

(2)老年代空間不足

(3)方法去空間不足

(4)通過Minor GC後進入老年代的平均大小大于老年代的可用記憶體

(5)由Eden區、From Space區向To Space區複制時,對象大小大于To Space可用記憶體,則把該對象轉存到老年代,且老年代的可用記憶體小于該對象大小

4. ★如何進行垃圾回收(垃圾回收算法)?

記憶體主要被分為三塊,新生代、舊生代、持久代。三代的特點不同,造就了他們所用的GC算法不同,新生代适合那些生命周期較短,頻繁建立及銷毀的對象,舊生代适合生命周期相對較長的對象,持久代在Sun HotSpot中就是指方法區(有些JVM中根本就沒有持久代這中說法)。首先介紹下新生代、舊生代、持久代的概念及特點:

垃圾回收機制(GC)1. 哪些“垃圾”需要回收?2. 那些對象被判斷為垃圾?3. 什麼時候回收垃圾4. ★如何進行垃圾回收(垃圾回收算法)?5. TLAB6.GC 相關參數總結

新生代:New Generation或者Young Generation。上面大緻分為Eden區和Survivor區,Survivor區又分為大小相同的兩部分:FromSpace 和ToSpace。建立的對象都是用新生代配置設定記憶體,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代的大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例.

舊生代:Old Generation。用于存放新生代中經過多次垃圾回收仍然存活的對象,例如緩存對象。舊生代占用大小為-Xmx值減去-Xmn對應的值。

持久代:Permanent Generation。在Sun的JVM中就是方法區的意思,盡管有些JVM大多沒有這一代。主要存放常量及類的一些資訊預設最小值為16MB,最大值為64MB,可通過-XX:PermSize及-XX:MaxPermSize來設定最小值和最大值。

常見的GC算法:

标記-清除算法(Mark-Sweep)

最基礎的GC算法,将需要進行回收的對象做标記,之後掃描,有标記的進行回收,這樣就産生兩個步驟:标記和清除。這個算法效率不高,而且在清理完成後會産生記憶體碎片,這樣,如果有大對象需要連續的記憶體空間時,還需要進行碎片整理,是以,此算法需要改進。

複制算法(Copying)

前面我們談過,新生代記憶體分為了三份,Eden區和2塊Survivor區,一般Sun的JVM會将Eden區和Survivor區的比例調為8:1,保證有一塊Survivor區是空閑的,這樣,在垃圾回收的時候,将不需要進行回收的對象放在空閑的Survivor區,然後将Eden區和第一塊Survivor區進行完全清理,這樣有一個問題,就是如果第二塊Survivor區的空間不夠大怎麼辦?這個時候,就需要當Survivor區不夠用的時候,暫時借持久代的記憶體用一下。此算法适用于新生代。

标記-整理(或叫壓縮)算法(Mark-Compact)

和标記-清楚算法前半段一樣,隻是在标記了不需要進行回收的對象後,将标記過的對象移動到一起,使得記憶體連續,這樣,隻要将标記邊界以外的記憶體清理就行了。此算法适用于持久代。

常見的垃圾收集器: 

根據上面說的諸多算法,每天JVM都有不同的實作,我們先來看看常見的一些垃圾收集器:

垃圾回收機制(GC)1. 哪些“垃圾”需要回收?2. 那些對象被判斷為垃圾?3. 什麼時候回收垃圾4. ★如何進行垃圾回收(垃圾回收算法)?5. TLAB6.GC 相關參數總結

首先介紹三種實際的垃圾回收器:串行GC(SerialGC)、并行回收GC(Parallel Scavenge)和并行GC(ParNew)。

1、Serial GC。是最基本、最古老的收集器,但是現在依然被廣泛使用,是一種單線程垃圾回收機制,而且不僅如此,它最大的特點就是在進行垃圾回收的時候,需要将所有正在執行的線程暫停(Stop The World),對于有些應用這是難以接受的,但是我們可以這樣想,隻要我們能夠做到将它所停頓的時間控制在N個毫秒範圍内,大多數應用我們還是可以接受的,而且事實是它并沒有讓我們失望,幾十毫米的停頓我們作為客戶機(Client)是完全可以接受的,該收集器适用于單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client級别預設的GC方式,可以通過-XX:+UseSerialGC來強制指定。

2、ParNew GC。基本和Serial GC一樣,但本質差別是加入了多線程機制,提高了效率,這樣它就可以被用在伺服器端(Server)上,同時它可以與CMS GC配合,是以,更加有理由将它置于Server端。

3、Parallel Scavenge GC。在整個掃描和複制過程采用多線程的方式來進行,适用于多CPU、對暫停時間要求較短的應用上,是server級别預設采用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數。以下給出幾組使用組合:

垃圾回收機制(GC)1. 哪些“垃圾”需要回收?2. 那些對象被判斷為垃圾?3. 什麼時候回收垃圾4. ★如何進行垃圾回收(垃圾回收算法)?5. TLAB6.GC 相關參數總結

4、CMS (Concurrent Mark Sweep)收集器。該收集器目标就是解決Serial GC 的停頓問題,以達到最短回收時間。常見的B/S架構的應用就适合用這種收集器,因為其高并發、高響應的特點。CMS收集器是基于“标記-清除”算法實作的,整個收集過程大緻分為4個步驟:

初始标記(CMS initial mark)、并發标記(CMS concurrenr mark)、重新标記(CMS remark)、并發清除(CMS concurrent sweep)。

其中初始标記、重新标記這兩個步驟任然需要停頓其他使用者線程。初始标記僅僅隻是标記出GC ROOTS能直接關聯到的對象,速度很快,并發标記階段是進行GC ROOTS 根搜尋算法階段,會判定對象是否存活。而重新标記階段則是為了修正并發标記期間,因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,這個階段的停頓時間會被初始标記階段稍長,但比并發标記階段要短。由于整個過程中耗時最長的并發标記和并發清除過程中,收集器線程都可以與使用者線程一起工作,是以整體來說,CMS收集器的記憶體回收過程是與使用者線程一起并發執行的。

CMS收集器的優點:并發收集、低停頓,但是CMS還遠遠達不到完美。

CMS收集器主要有三個顯著缺點:

a>.CMS收集器對CPU資源非常敏感。在并發階段,雖然不會導緻使用者線程停頓,但是會占用CPU資源而導緻引用程式變慢,總吞吐量下降。CMS預設啟動的回收線程數是:(CPU數量+3) / 4。

b>.CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導緻另一次Full GC的産生。由于CMS并發清理階段使用者線程還在運作,伴随程式的運作自熱會有新的垃圾不斷産生,這一部分垃圾出現在标記過程之後,CMS無法在本次收集中處理它們,隻好留待下一次GC時将其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由于在垃圾收集階段使用者線程還需要運作,即需要預留足夠的記憶體空間給使用者線程使用,是以CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分記憶體空間提供并發收集時的程式運作使用。在預設設定下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低記憶體回收次數提高性能。要是CMS運作期間預留的記憶體無法滿足程式其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛拟機将啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。是以說參數-XX:CMSInitiatingOccupancyFraction設定的過高将會很容易導緻“Concurrent Mode Failure”失敗,性能反而降低。

c>.最後一個缺點,CMS是基于“标記-清除”算法實作的收集器,使用“标記-清除”算法收集後,會産生大量碎片。空間碎片太多時,将會給對象配置設定帶來很多麻煩,比如說大對象,記憶體空間找不到連續的空間來配置設定不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用于在Full GC之後增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數設定執行多少次不壓縮的Full GC之後,跟着來一次碎片整理過程。

5、G1收集器。相比CMS收集器有不少改進,首先基于标記-整理算法,不會産生記憶體碎片問題,其次,可以比較精确的控制停頓,此處不再詳細介紹。

6、Serial Old。Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“标記-整理”算法。主要使用在Client模式下的虛拟機。

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

8、RTSJ垃圾收集器,用于Java實時程式設計。

5. TLAB

5.1什麼是TLAB

TLAB全稱ThreadLocalAllocBuffer,是線程的一塊私有記憶體,如果設定了虛拟機參數-XX:UseTLAB,線上程初始化時,同時也會申請一塊指定大小的記憶體,隻給目前線程使用,這樣每個線程都單獨擁有一個緩沖區,如果需要配置設定記憶體,就在自己的緩沖區上配置設定,這樣就不存在競争的情況,可以大大提升配置設定效率,當緩存容量不夠的時候,再重新從伊甸園區域申請一塊繼續使用,這個申請動作還是需要原子操作的。

TLAB的目的是在為新對象配置設定記憶體空間時,讓每個Java的應用線程能在使用自己專屬的配置設定指針來配置設定空間,均攤對GC堆(伊甸園區)裡共享的配置設定指針做更新而帶來的同步開銷。

TLAB隻是讓每個線程有私有的配置設定指針,但底下存對象的記憶體空間還是給所有線程通路的,隻是其它線程無法在這個區域配置設定而已。當一個TLAB用滿(配置設定指針頂部撞上配置設定極限端了),就新申請一個TLAB,而在老TLAB裡的對象還留在原地什麼都不用管 - 它們無法感覺自己是否是曾經從TLAB配置設定出來的,而隻關心自己是在伊甸裡配置設定的。

即線程本地配置設定緩存區,這是一個線程專用的記憶體配置設定區域。 

由于對象一般會配置設定在堆上,而堆是全局共享的。是以在同一時間,可能會有多個線程在堆上申請空間。是以,每次對象配置設定都必須要進行同步(虛拟機采用CAS配上失敗重試的方式保證更新操作的原子性),而在競争激烈的場合配置設定的效率又會進一步下降。JVM使用TLAB來避免多線程沖突,在給對象配置設定記憶體時,每個線程使用自己的TLAB,這樣可以避免線程同步,提高了對象配置設定的效率。

每個線程會從Eden配置設定一大塊空間,例如說100KB,作為自己的TLAB。這個start是TLAB的起始位址,end是TLAB的末尾,然後top是目前的配置設定指針。顯然start <= top < end。

當一個Java線程在自己的TLAB中配置設定到盡頭之後,再要配置設定就會出發一次“TLAB refill”,也就是說之前自己的TLAB就“不管了”(所有權交回給共享的Eden),然後重新從Eden裡配置設定一塊空間作為新的TLAB。所謂“不管了”并不是說就讓舊TLAB裡的對象直接死掉,而是把那塊空間的控制權歸還給普通的Eden,裡面的對象該怎樣還是怎樣。通常情況下,在TLAB中配置設定多次才會填滿TLAB、觸發TLAB refill,這樣使用TLAB配置設定就比直接從共享部分的Eden配置設定要均攤(amortized)了同步開銷,于是提高了性能。其實很多關注多線程性能的malloc庫實作也會使用類似的做法,例如TCMalloc。

到觸發GC的時候,無論是minor GC還是full GC,要收集Eden的時候裡面的空間無論是屬于某個線程的TLAB還是不屬于任何TLAB都一視同仁,把Eden當作一個整體來收集裡面的對象——把活的對象拷貝到survivor space(或者直接晉升到Old Gen)。在GC結束之後,每個Java線程又會重新從Eden配置設定自己的TLAB。周而複始。

線程逃逸: 

我們把指向剛配置設定出來的Test執行個體的引用指派到了一個靜态變量或者可以被其他線程通路的執行個體字段上時,就可能導緻别的線程可以感覺到這個新對象的存在,是以這種動作也叫做“釋出”(publish)或者叫做“線程逃逸”(thread escaping)。

如果HotSpot VM要實作前面提到的TLGC的話,那就必須要線上程逃逸發生的時候做一些特殊處理了。 

所謂特殊處理可以是在發生線程逃逸時觸發一次minor GC來把目前TLAB裡有被共享變量所引用的對象移動到Eden的共享部分去,這種動作叫做“全局化”(globalization)。也可以有别的做法,例如說在發生線程逃逸時先做些标記而不立即觸發全局化,想辦法把全局化GC推遲一點做,這樣可以更高效一些。全局化GC跟普通的minor GC開銷差不多,如果一個線程在期望的觸發正常TLGC之前觸發了一次或多次全局化GC的話,做TLGC就得不償失了。正是因為如何高效處理全局化是個很麻煩、需要非常細緻地處理的事情,是以HotSpot VM才遲遲沒有把這個功能做到主幹版本上。

5.2TLAB實作

實作位于

/Users/zhanjun/openjdk/hotspot/src/share/vm/memory/threadLocalAllocBuffer.hpp

TLAB簡單來說本質上就是三個指針:start,top和end,每個線程都會從Eden配置設定一大塊空間,例如說100KB,作為自己的TLAB,其中start和end是占位用的,辨別出eden裡被這個TLAB所管理的區域,卡住eden裡的一塊空間不讓其它線程來這裡配置設定。而top就是裡面的配置設定指針,一開始指向跟start同樣的位置,然後逐漸配置設定,直到再要配置設定下一個對象就會撞上結束的時候就會觸發一次TLAB refill,refill過程後續會解釋。

_desired_size是指TLAB的記憶體大小。

_refill_waste_limit是指最大的浪費空間,假設為5KB,通俗一點講就是:

1,假如目前TLAB已經配置設定96KB,還剩下4KB,但是現在新了一個對象需要6KB的空間,顯然TLAB的記憶體不夠了,這時可以簡單的重新申請一個TLAB,原先的TLAB交給Eden管理,這時隻浪費4KB的空間,在_refill_waste_limit之内

.2,假如目前TLAB已經配置設定90KB,還剩下10KB,現在新了了一個對象需要11KB,顯然TLAB的記憶體不夠了,這時就不能簡單的抛棄目前TLAB,這11KB會被安排到伊甸園區進行申請。

5.3記憶體配置設定

1,如果目前TLAB的剩餘容量大于浪費門檻值,就不在目前TLA配置設定,直接在共享的Eden區進行配置設定,并且記錄慢配置設定的記憶體大小;

2,如果剩餘容量小于浪費門檻值,說明可以丢棄目前TLAB了;

3,通過allocate_new_tlab()方法,從伊登新配置設定一塊裸的空間出來(這一步可能會失敗),如果失敗說明eden沒有足夠空間來配置設定這個新TLAB,就會觸發YGC。

申請好新的TLAB記憶體之後,執行TLAB的fill()方法,fill()方法包括下述幾個動作:

1,統計refill 的次數

2,初始化重新申請到的記憶體塊

3,将目前TLAB抛棄(退休)掉,這個過程中最重要的動作是将TLAB末尾尚未配置設定給Java對象的空間(浪費掉的空間)配置設定成一個假的“filler object”(目前是用int []作為填充對象)。這是為了保持GC堆可以線性解析(heap parseability)用的。

6.GC 相關參數總結

6.1與串行回收器相關的參數

-XX:+UseSerialGC:在新生代和老年代使用串行回收器。

-XX:+SurvivorRatio:設定 eden 區大小和 survivor 區大小的比例。

-XX:+PretenureSizeThreshold:設定大對象直接進入老年代的門檻值。當對象的大小超過這個值時,将直接在老年代配置設定。

-XX:MaxTenuringThreshold:設定對象進入老年代的年齡的最大值。每一次 Minor GC 後,對象年齡就加 1。任何大于這個年齡的對象,一定會進入老年代。

6.2與并行 GC 相關的參數

-XX:+UseParNewGC: 在新生代使用并行收集器。

-XX:+UseParallelOldGC: 老年代使用并行回收收集器。

-XX:ParallelGCThreads:設定用于垃圾回收的線程數。通常情況下可以和 CPU 數量相等。但在 CPU 數量比較多的情況下,設定相對較小的數值也是合理的。

-XX:MaxGCPauseMills:設定最大垃圾收集停頓時間。它的值是一個大于 0 的整數。收集器在工作時,會調整 Java 堆大小或者其他一些參數,盡可能地把停頓時間控制在 MaxGCPauseMills 以内。

-XX:GCTimeRatio:設定吞吐量大小,它的值是一個 0-100 之間的整數。假設 GCTimeRatio 的值為 n,那麼系統将花費不超過 1/(1+n) 的時間用于垃圾收集。

-XX:+UseAdaptiveSizePolicy:打開自适應 GC 政策。在這種模式下,新生代的大小,eden 和 survivor 的比例、晉升老年代的對象年齡等參數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。

6.3與 CMS 回收器相關的參數

-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。

-XX:+ParallelCMSThreads: 設定 CMS 的線程數量。

-XX:+CMSInitiatingOccupancyFraction:設定 CMS 收集器在老年代空間被使用多少後觸發,預設為 68%。

-XX:+UseFullGCsBeforeCompaction:設定進行多少次 CMS 垃圾回收後,進行一次記憶體壓縮。

-XX:+CMSClassUnloadingEnabled:允許對類中繼資料進行回收。

-XX:+CMSParallelRemarkEndable:啟用并行重标記。

-XX:CMSInitatingPermOccupancyFraction:當永久區占用率達到這一百分比後,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

-XX:UseCMSInitatingOccupancyOnly:表示隻在到達門檻值的時候,才進行 CMS 回收。

-XX:+CMSIncrementalMode:使用增量模式,比較适合單 CPU。

6.4與 G1 回收器相關的參數

-XX:+UseG1GC:使用 G1 回收器。

-XX:+UnlockExperimentalVMOptions:允許使用實驗性參數。

-XX:+MaxGCPauseMills:設定最大垃圾收集停頓時間。

-XX:+GCPauseIntervalMills:設定停頓間隔時間。

6.5其他參數

-XX:+DisableExplicitGC: 禁用顯示 GC。