柚子快報邀請碼778899分享:java JVM(四)
柚子快報邀請碼778899分享:java JVM(四)
目錄
垃圾回收算法垃圾回收算法的評價標準吞吐量(Throughput)最大暫停時間堆使用效率
算法權衡標記-清除算法(Mark and Sweep)標記-整理算法(Mark and Compact)復制算法(Copying)分代算法(Generational)回收過程
垃圾回收器Serial - Serial Old年輕代 Serial 垃圾回收器老年代 Serial Old 垃圾回收器
ParNew - CMS年輕代 ParNew 垃圾回收器老年代 CMS(Concurrent Mark Sweep)垃圾回收器
Parallel Scavenge - Parallel Old年輕代 Parallel Scavenge 垃圾回收器老年代 Parallel Old 垃圾回收器
G1(Garbage-First)垃圾收集器內(nèi)存結構年輕代回收混合回收(Mixed GC)
垃圾回收器的選擇
垃圾回收算法
Java 虛擬機通過釋放不再存活的對象所占用的內(nèi)存空間實現(xiàn)垃圾回收,尋找不再存活的對象的過程使用到的就是垃圾回收算法。
垃圾回收算法是計算機科學領域中用于管理動態(tài)分配的內(nèi)存的一種技術。在使用動態(tài)內(nèi)存分配的編程語言中,如 Java、C# 等,程序員可以通過申請內(nèi)存來創(chuàng)建對象和數(shù)據(jù)結構,但是不再需要這些對象時,程序員通常需要手動釋放它們所占用的內(nèi)存。如果程序員忘記釋放這些內(nèi)存或者釋放不當,就會導致內(nèi)存泄漏,即分配的內(nèi)存不再使用但仍然被占用,從而浪費系統(tǒng)資源并可能導致程序性能下降。
垃圾回收算法的主要目標是自動化管理內(nèi)存的分配和釋放,以便程序員無需手動干預。它的基本思想是通過識別和清除不再被程序引用的對象來回收它們所占用的內(nèi)存,從而減少內(nèi)存泄漏的風險并優(yōu)化系統(tǒng)的資源利用率。
1960年 John McCarthy 發(fā)布了第一個GC 算法:標記-清除算法。
1963年 Marvin L. Minsky 發(fā)布了復制算法。
本質上后續(xù)所有的垃圾回收算法,都是在上述兩種算法的基礎上優(yōu)化而來。
垃圾回收算法的評價標準
Java 垃圾回收過程會通過單獨的 GC 線程來完成,但是不管使用哪一種 GC 算法,都會有部分階段需要停止所有的用戶線程。這個過程被稱之為 Stop The World 簡稱 STW,如果 STW 時間過長則會影響用戶的使用。 判斷 GC 算法是否優(yōu)秀,可以從三個方面來考慮:
吞吐量(Throughput)
吞吐量(Throughput)是評估垃圾回收算法性能的重要指標之一,特別是在需要處理大量數(shù)據(jù)或高并發(fā)的應用程序中。它通常用來衡量系統(tǒng)在執(zhí)行某項任務時的效率,一般用 CPU 用于執(zhí)行用戶代碼的時間與 CPU 總執(zhí)行時間的比值作為評價指標,吞吐量數(shù)值越高,垃圾回收的效率就越高。吞吐量一般可以用以下公式計算:
吞吐量 = 執(zhí)行用戶代碼時間 / (執(zhí)行用戶代碼時間 + GC 時間)
如果垃圾回收時間很少,那么吞吐量接近于 1,表示大部分時間都用于執(zhí)行用戶代碼,系統(tǒng)效率高。如果垃圾回收時間很多,吞吐量會減小。
最大暫停時間
最大暫停時間指的是所有在垃圾回收過程中的 STW 時間最大值。比如如下的圖中,黃色部分的 STW 就是最大暫停時間,顯而易見上面的圖比下面的圖擁有更少的最大暫停時間。最大暫停時間越短,用戶使用系統(tǒng)時受到的影響就越短。
堆使用效率
不同垃圾回收算法,對堆內(nèi)存的使用方式是不同的。比如標記清除算法,可以使用完整的堆內(nèi)存。而復制算法會將堆內(nèi)存一分為二,每次只能使用一半內(nèi)存。從堆使用效率上來說,標記清除算法要優(yōu)于復制算法。
算法權衡
上述三種評價標準:堆使用效率、吞吐量,以及最大暫停時間不可兼得。一般來說,堆內(nèi)存越大,最大暫停時間就越長。
堆大小的增加通常會導致垃圾回收的頻率降低,因為系統(tǒng)有更多的空間來容納對象。然而,當堆大小增加時,單次垃圾回收的時間也可能增加,這可能會導致較長的最大暫停時間。
因此,為了降低最大暫停時間,可以選擇減小堆大小,但這可能會影響堆使用效率和吞吐量。
想要減少最大暫停時間,就會降低吞吐量。不同的垃圾回收算法,適用于不同的場景。
標記-清除算法(Mark and Sweep)
這是最基本的垃圾回收算法之一。它分為兩個階段:標記階段和清除階段。
標記階段:從根對象開始,通過可達性分析,標記所有被使用的對象。清除階段:清除所有未被標記的對象,釋放它們所占用的內(nèi)存空間。這個階段可能會導致內(nèi)存碎片化。
優(yōu)點:實現(xiàn)簡單,只需要在第一階段給每個對象維護標志位,第二階段刪除對象即可。 缺點:
碎片化問題:由于內(nèi)存是連續(xù)的,所以在對象被刪除之后,內(nèi)存中會出現(xiàn)很多細小的可用內(nèi)存單元。如果我們需要的是一個比較大的空間,很有可能這些內(nèi)存單元的大小過小無法進行分配。 分配速度慢:由于內(nèi)存碎片的存在,Java 虛擬機需要在內(nèi)存中維護一個空閑鏈表,極有可能發(fā)生每次需要遍歷到鏈表的最后才能獲得合適的內(nèi)存空間。
標記-整理算法(Mark and Compact)
標記-整理算法也叫標記壓縮算法,是對標記-清理算法中容易產(chǎn)生內(nèi)存碎片問題的一種解決方案。核心思想分為兩個階段:
標記階段:將所有存活的對象進行標記。整理階段:將存活對象移動到堆的一端,然后直接清理掉邊界外的內(nèi)存。
優(yōu)點:
內(nèi)存使用效率高:整個堆內(nèi)存都可以使用,不會像復制算法只能使用半個堆內(nèi)存不會發(fā)生碎片化:在整理階段可以將對象往內(nèi)存的一側進行移動,剩下的空間都是可以分配對象的空間。
缺點:整理階段的效率不高
復制算法(Copying)
復制算法的核心思想是:
準備兩塊空間 From 空間和 To 空間,每次在對象分配階段,只能使用其中一塊空間(From 空間)。在垃圾回收階段,將 From 中存活對象復制到 To 空間。清理 From 空間,并把 From 和 To 名稱互換
優(yōu)點:
吞吐量高:復制算法只需要遍歷一次存活對象復制到 To 空間即可,比標記-整理算法少了一次遍歷的過程,因而性能較好,但是不如標記-清除算法,因為標記-清除算法不需要進行對象的移動。不會發(fā)生碎片化:復制算法在復制之后就會將對象按順序放入 To 空間中,所以對象以外的區(qū)域都是可用空間,不存在碎片化內(nèi)存空間。
缺點:
內(nèi)存使用率低:每次只能讓一半的內(nèi)存空間來為創(chuàng)建對象使用。
分代算法(Generational)
分代算法是一種應用最廣的垃圾回收算法,分代算法認為大多數(shù)對象的生命周期非常短暫,基于這一假設,它將堆內(nèi)存劃分為不同的區(qū)域(或稱為代),并根據(jù)對象的生命周期將不同的垃圾回收算法應用于不同的區(qū)域。
通常情況下,JVM的堆內(nèi)存被劃分為兩個主要的代:
新生代(Young Generation):新生代是年輕對象的存放區(qū)域。新生代一般被劃分為兩部分:Eden 區(qū)(伊甸園區(qū))和兩個 Survivor區(qū)(通常是 From 區(qū)和 To 區(qū))。老年代(Old Generation 或 Tenured Generation):老年代主要存放長期存活的對象。
回收過程
初始時,新生代一般由三部分組成:Eden 區(qū)和兩個 Survivor 區(qū) S0 和 S1,分別對應 From 區(qū)和 To 區(qū)。當應用程序創(chuàng)建新的對象時,對象會被分配到 Eden 區(qū)。隨著對象在 Eden 區(qū)越來越多,如果 Eden 區(qū)滿,新創(chuàng)建的對象已經(jīng)無法放入,就會觸發(fā)年輕代的GC,稱為 Minor GC 或者 Young GC。Minor GC 的目標是清理 Eden 區(qū)和 From 區(qū),將仍然存活的對象移到 To 區(qū)(參考復制算法)。
垃圾收集器開始時,會標記所有在 Eden 區(qū)和 From 區(qū)中存活的對象。未被標記的對象被視為垃圾。標記階段結束后,垃圾收集器會將存活的對象復制到 To 區(qū),并清理 Eden 區(qū)和 From 區(qū),接下來,S0 會變成 To 區(qū),S1 變成 From 區(qū)。當 Eden 區(qū)滿時再往里放入對象,依然會發(fā)生 Minor GC 。
此時會回收 Eden 區(qū)和 S1(From)中的對象,并把 Eden 和 From 區(qū)中剩余的對象放入 S0,垃圾清理后,S0 又變成 From 區(qū),S1 又變成 To 區(qū)。
對象在 Survivor 區(qū)每經(jīng)過一次 Minor GC,其年齡會增加。當達到一定年齡閾值(通常是15歲),對象將會被晉升到老年代。
當老年代無法為新的大對象分配足夠的空間,或者老年代中存放的對象已經(jīng)占滿了老年代空間時,會觸發(fā) Full GC 來嘗試釋放未使用的對象,以騰出空間。Full GC 會對整個堆進行垃圾回收。如果 Full GC 依然無法回收掉老年代的對象,那么當對象繼續(xù)放入老年代時,就會拋出 Out Of Memory 異常。
使用以下JVM參數(shù),可以調整新生代中 Eden 空間與 Survivor 空間的比例:
-XX:SurvivorRatio
例如,如果設置 -XX:SurvivorRatio=8,表示每個 Survivor 空間的大小是 Eden 空間大小的 1/8
垃圾回收器
垃圾回收器是垃圾回收算法的具體實現(xiàn),Java虛擬機中的垃圾回收器有多種類型,由于垃圾回收器分為年輕代和老年代,除了 G1 之外其他垃圾回收器必須成對組合進行使用。具體的組合關系如下圖:
總結起來,目前還在流行的就是以下幾種:
Serial - Serial OldParNew - CMSParallel Scavenge - Parallel OldG1
Serial - Serial Old
使用以下虛擬機參數(shù)可以指定使用 Java 虛擬機使用 Serial 垃圾回收器組合,即同時使用 Serial 收集器(新生代)和 Serial Old 收集器(老年代):
-XX:+UseSerialGC
年輕代 Serial 垃圾回收器
Serial 垃圾回收器是 Java 虛擬機中最基本的垃圾回收器之一,主要用于新生代的垃圾回收。它的特點和工作原理如下:
工作方式:Serial 垃圾回收器使用單線程進行垃圾回收操作,因此也稱為串行垃圾回收器。它通過復制算法算來管理新生代的內(nèi)存空間。Stop-the-World:在進行垃圾回收時,Serial 垃圾回收器會觸發(fā)一個停止所有應用線程的停頓事件,這被稱為“Stop-the-World”事件。在這個時間段內(nèi),所有應用程序的執(zhí)行都會暫停,直到垃圾回收完成。適用場景:由于其單線程執(zhí)行的特點,Serial 垃圾回收器適合于簡單的客戶端應用程序或者對資源要求較少的場景。它的主要優(yōu)點是實現(xiàn)簡單,對系統(tǒng)資源消耗較低,但也因為單線程執(zhí)行的緣故,在多核處理器上不能充分利用硬件并行性能。配對關系:Serial 垃圾回收器通常與 Serial Old 垃圾回收器組合使用,形成串行收集器組合。這種組合適合于資源有限、對垃圾回收性能要求不高的場景。
老年代 Serial Old 垃圾回收器
Serial Old 是 Serial 收集器在老年代的應用。它的特點和工作原理如下:
單線程執(zhí)行:和 Serial 收集器一樣,Serial Old 也是單線程執(zhí)行的。這意味著在進行老年代的垃圾回收時,只有一個線程在執(zhí)行回收操作。這種設計簡單而有效,但也限制了其在多核處理器上的并行能力。標記-整理算法:Serial Old 使用標記-整理(Mark-Compact)算法進行垃圾回收。這種算法首先標記出所有存活的對象,然后將它們向一端移動,之后清理掉未使用的內(nèi)存空間,從而整理出連續(xù)的內(nèi)存空間,為新對象的分配提供空間。適用場景:Serial Old 主要適用于簡單的客戶端應用或者小型應用場景,例如桌面應用或者低流量的服務端應用。這些應用通常對資源消耗和垃圾回收停頓時間的要求相對較低,可以接受偶爾的較長停頓時間。配對關系:Serial Old 垃圾回收器通常與 Serial 垃圾回收器組合使用,形成串行收集器組合。這種組合適合于資源有限、對垃圾回收性能要求不高的場景。
ParNew - CMS
使用以下虛擬機參數(shù)可以指定使用 Java 虛擬機指定使用 ParNew 垃圾回收器(新生代):
-XX UseParNewGC
使用以下虛擬機參數(shù)可以指定使用 Java 虛擬機指定使用 CMS 垃圾回收器(老年代):
-XX:+UseConcMarkSweepGC
年輕代 ParNew 垃圾回收器
ParNew 垃圾回收器是用于新生代的并行垃圾回收器。它主要與 CMS 垃圾回收器結合使用,用于提高新生代的垃圾回收效率。它的特點和工作原理如下:
并行執(zhí)行:ParNew 垃圾回收器使用多線程并行執(zhí)行垃圾回收操作。與 Serial 垃圾回收器不同,ParNew 可以利用多核處理器的優(yōu)勢,在多個線程之間并行地執(zhí)行垃圾回收任務,從而提高回收效率。與 CMS 垃圾回收器結合:ParNew 垃圾回收器通常與 CMS 垃圾回收器組合使用。CMS 負責老年代的并發(fā)標記和清理,而 ParNew 則負責新生代的并行垃圾回收。這種組合能夠在盡量減少應用程序停頓的同時,高效地管理整個堆內(nèi)存。Stop-the-World:盡管 ParNew 使用并行執(zhí)行,但在執(zhí)行 Full GC 時,仍然會觸發(fā)停頓事件(Stop-the-World)。這時所有的應用線程都會暫停,以確保垃圾回收器能夠正確地清理整個堆內(nèi)存。性能調優(yōu):可以通過 JVM 的啟動參數(shù)來配置 ParNew 垃圾回收器的線程數(shù)目、堆內(nèi)存大小等參數(shù),以達到最佳的性能和響應時間。
老年代 CMS(Concurrent Mark Sweep)垃圾回收器
CMS 垃圾回收器使用并發(fā)標記(Concurrent Marking)來避免在大部分回收過程中停止應用程序的執(zhí)行。它的核心思想是在盡可能短的停頓時間內(nèi)完成大部分垃圾回收工作。
CMS執(zhí)行步驟:
初始標記:用極短的時間標記出 GC Root 能直接關聯(lián)到的對象。并發(fā)標記:并發(fā)地標記出所有存活的對象,用戶線程不需要暫停。重新標記:由于并發(fā)標記階段有些對象會發(fā)生了變化,存在錯標、漏標等情況,需要重新標記。并發(fā)清理:清理死亡的對象,用戶線程不需要暫停。
CMS垃圾回收器存在的問題:
CMS 使用了標記-清除算法,在垃圾收集結束之后會出現(xiàn)大量的內(nèi)存碎片,CMS 會在 Full GC 時進行碎片的整理。這樣會導致用戶線程暫停,以下虛擬機參數(shù)指定次數(shù)不壓縮的 Full GC 后,執(zhí)行一次帶壓縮的 Full GC。默認值為0,表示每次進入 Full GC 時都進行碎片整理:-XX:CMSFullGCsBeforeCompaction=N
無法處理在并發(fā)清理過程中產(chǎn)生的浮動垃圾,不能做到完全的垃圾回收,也就是 CMS 過程中最后一步時候產(chǎn)生的垃圾對象沒有辦法被清理。如果老年代內(nèi)存不足無法分配對象,CMS 就會退化成 Serial Old 單線程回收老年代。
Parallel Scavenge - Parallel Old
使用以下虛擬機參數(shù)任意一個,都可以告訴虛擬機使用這個組合:
-XX:+UseParallelGC
-XX:+UseParallelOldGC
年輕代 Parallel Scavenge 垃圾回收器
Parallel Scavenge是 JDK8 默認的年輕代垃圾回收器,多線程并行回收,關注的是系統(tǒng)的吞吐量。具備自動調整堆內(nèi)存大小的特點。
Parallel Scavenge允許手動設置最大暫停時間和吞吐量,垃圾回收器會通過自動調整堆大小盡量滿足設置的最大暫停時間和吞吐量,當堆較小時,回收所需要的時間自然會縮短,從而提升了最大暫停時間和吞吐量。
Oracle 官方建議在使用這個組合時,不要設置堆內(nèi)存的最大值,垃圾回收器會根據(jù)最大暫停時間和吞吐量自動調整內(nèi)存大小。
使用以下虛擬機參數(shù)可以設置期望的最大垃圾收集停頓時間,單位毫秒:
-XX:MaxGCPauseMillis=n
使用以下虛擬機參數(shù)可以設置垃圾收集時間占總時間的比例,套入公式 1 / (1 + n) 就能得到具體的比例,假設此處 n 設置為19,則比例為 5%:
--GCTimeRatio=n
以下虛擬機參數(shù)可以讓垃圾回收器根據(jù)吞吐量和最大停頓的毫秒數(shù)自動調整內(nèi)存大小
-XX:+UseAdaptiveSizePolicy
老年代 Parallel Old 垃圾回收器
Parallel Old 垃圾收集器與 Parallel Scavenge 垃圾收集器配合使用,可以實現(xiàn)全局并行的垃圾收集。它們都使用多線程來加速垃圾收集過程,以提高垃圾收集的吞吐量和效率。
G1(Garbage-First)垃圾收集器
G1(Garbage-First)垃圾回收器是Java虛擬機中一種現(xiàn)代化的垃圾回收器,它在 JDK 7 中首次推出,并在 JDK 9 及以后的版本中繼續(xù)得到改進和優(yōu)化。G1 能夠同時處理新生代和老年代的垃圾回收,不像之前介紹的那些要配對使用。JDK 9 之后默認的垃圾回收器是 G1 垃圾回收器。Parallel Scavenge 關注吞吐量,允許用戶設置最大暫停時間,但是會減少年輕代可用空間的大小。CMS 關注暫停時間,但是吞吐量方面會下降。而 G1 設計目標就是將上述兩種垃圾回收器的優(yōu)點融合:
支持巨大的堆空間回收,并有較高的吞吐量。支持多 CPU 并行垃圾回收。允許用戶設置最大暫停時間。
JDK8 的較新版本和 JDK9 之后強烈建議使用 G1 垃圾回收器,使用以下虛擬機參數(shù)可以開啟 G1,JDK 9 以后默認使用 G1:
-XX:+UseG1GC
內(nèi)存結構
G1 的整個堆會被劃分成多個大小相等的區(qū)域,稱之為區(qū)(Region),區(qū)不要求是連續(xù)的。分為 Eden 、Survivor 、Old 區(qū)。Region 的大小通過(堆空間大小 / 2048)計算得到。
通過以下虛擬機參數(shù)可以指定 Region的大小,Region 大小必須是2的指數(shù)冪,取值范圍從1M 到 32M:
-XX:G1HeapRegionSize=32m
G1 垃圾回收器有兩種回收方式:
年輕代回收(Young GC) - 只處理 Eden 和 Survivor 等年輕代區(qū)域混合回收(Mixed GC)- 年輕代和老年代都會處理
年輕代回收
年輕代回收(Young GC),回收 Eden 區(qū)和 Survivor 區(qū)中不用的對象。會導致STW,G1 中可以通過參數(shù),通過設置每次垃圾回收時的最大暫停時間毫秒數(shù),G1 垃圾回收器會盡可能地保證暫停時間。
-XX:MaxGCPauseMillis=200
新創(chuàng)建的對象會存放在 Eden 區(qū)。當 G1 判斷年輕代區(qū)不足,無法分配對象時需要回收時會執(zhí)行Young GC。Young GC 首先會標記出 Eden 和 Survivor 區(qū)域中的存活對象。根據(jù)配置的最大暫停時間選擇某些區(qū)域將存活對象復制到一個新的 Survivor 區(qū)中(年齡+1),然后清空這些區(qū)域。
G1 在進行 Young GC 的過程中會去記錄每次垃圾回收時每個 Eden 區(qū)和 Survivor 區(qū)的平均耗時,以作為下次回收時的參考依據(jù)。這樣就可以根據(jù)配置的最大暫停時間計算出本次回收時最多能回收多少個 Region 區(qū)域了。例如,最大暫停時間200毫秒,平均每個 Region 回收需要40毫秒,那么這次回收最多只能回收 4個 Region。
后續(xù) Young GC 時與之前相同,只不過 Survivor 區(qū)中存活對象會被搬運到另一個 Survivor 區(qū),當某個存活對象的年齡到達閾值(默認15),將被放入老年代。
部分對象如果大小超過 Region 的一半,會直接放入老年代,這類老年代被稱為 Humongous 區(qū)。比如堆內(nèi)存是4G,每個 Region 是2M,只要一個大對象超過了 1M 就被放入 Humongous 區(qū),如果對象過大會橫跨多個Region 。
混合回收(Mixed GC)
多次回收之后,會出現(xiàn)很多 Old 老年代區(qū),此時總堆占有率達到閾值時(默認為 45%)會啟動 Mixed GC 回收所有年輕代和部分老年代的對象以及大對象區(qū)。采用復制算法來完成。通過以下虛擬機參數(shù)可以配置這個閾值:
-XX:InitiatingHeapOccupancyPercent=45
混合回收的基本步驟:
初始標記階段(Initial Marking Phase):標記 GC Root 直接引用的對象為存活。并發(fā)標記階段(Concurrent Marking Phase):將第一步中標記的對象引用的對象,標記為存活。最終標記階段(Final Marking Phase):處理在并發(fā)標記期間應用程序有可能更新的引用關系,確保標記的準確性。并發(fā)清理階段(Concurrent Cleanup Phase):將存活對象復制到別的 Region,由于使用復制算法,不會產(chǎn)生內(nèi)存碎片。
G1 對老年代的清理會優(yōu)先選擇存活度最低的區(qū)域(就是垃圾最多)來進行回收,這樣可以保證回收效率最高,這也是G1(Garbage first)名稱的由來。 如果清理過程中發(fā)現(xiàn)沒有足夠的空 Region 存放轉移的對象,會出現(xiàn) Full GC 。單線程執(zhí)行標記-整理算法,此時會導致用戶線程的暫停。所以盡量保證應該用的堆內(nèi)存有一定多余的空間。
垃圾回收器的選擇
垃圾回收器的組合關系雖然很多,但是針對幾個特定的版本,比較好的組合選擇如下:
JDK8 及之前:ParNew + CMS(關注暫停時間)、Parallel Scavenge + Parallel Old (關注吞吐量) 、G1(JDK8的前期版本及JDK8前的版本不建議,因為不太完善較,適用于堆內(nèi)存較大并且關注暫停時間)JDK9 之后:G1(默認),從 JDK9 之后,由于 G1 日趨成熟,JDK 默認的垃圾回收器已經(jīng)修改為G1,所以強烈建議在生產(chǎn)環(huán)境上使用G1。
柚子快報邀請碼778899分享:java JVM(四)
相關文章
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。