亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JVM內存管理深入垃圾收集器與內存分配策略的示例分析

發布時間:2021-10-23 17:06:55 來源:億速云 閱讀:144 作者:柒染 欄目:編程語言

這篇文章給大家介紹JVM內存管理深入垃圾收集器與內存分配策略的示例分析,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的高墻,墻外面的人想進去,墻里面的人卻想出來。

概述:

說起垃圾收集(Garbage Collection,下文簡稱GC),大部分人都把這項技術當做Java語言的伴生產物。事實上GC的歷史遠遠比Java來得久遠,在1960年誕生于MIT的Lisp是***門真正使用內存動態分配和垃圾收集技術的語言。當Lisp還在胚胎時期,人們就在思考GC需要完成的3件事情:哪些內存需要回收?什么時候回收?怎么樣回收?

經過半個世紀的發展,目前的內存分配策略與垃圾回收技術已經相當成熟,一切看起來都進入“自動化”的時代,那為什么我們還要去了解GC和內存分配?答案很簡單:當需要排查各種內存溢出、泄漏問題時,當垃圾收集成為系統達到更高并發量的瓶頸時,我們就需要對這些“自動化”的技術有必要的監控、調節手段。

把時間從1960年撥回現在,回到我們熟悉的Java語言。本文***章中介紹了Java內存運行時區域的各個部分,其中程序計數器、VM棧、本地方法棧三個區域隨線程而生,隨線程而滅;棧中的幀隨著方法進入、退出而有條不紊的進行著出棧入棧操作;每一個幀中分配多少內存基本上是在Class文件生成時就已知的(可能會由JIT動態晚期編譯進行一些優化,但大體上可以認為是編譯期可知的),因此這幾個區域的內存分配和回收具備很高的確定性,因此在這幾個區域不需要過多考慮回收的問題。而Java堆和方法區(包括運行時常量池)則不一樣,我們必須等到程序實際運行期間才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,我們本文后續討論中的“內存”分配與回收僅僅指這一部分內存。

對象已死?

在堆里面存放著Java世界中幾乎所有的對象,在回收前首先要確定這些對象之中哪些還在存活,哪些已經“死去”了,即不可能再被任何途徑使用的對象。

引用計數算法(Reference Counting)

最初的想法,也是很多教科書判斷對象是否存活的算法是這樣的:給對象中添加一個引用計數器,當有一個地方引用它,計數器加1,當引用失效,計數器減1,任何時刻計數器為0的對象就是不可能再被使用的。

客觀的說,引用計數算法實現簡單,判定效率很高,在大部分情況下它都是一個不錯的算法,但引用計數算法無法解決對象循環引用的問題。舉個簡單的例子:對象A和B分別有字段b、a,令A.b=B和B.a=A,除此之外這2個對象再無任何引用,那實際上這2個對象已經不可能再被訪問,但是引用計數算法卻無法回收他們。

根搜索算法(GC Roots Tracing)

在實際生產的語言中(Java、C#、甚至包括前面提到的Lisp),都是使用根搜索算法判定對象是否存活。算法基本思路就是通過一系列的稱為“GC Roots”的點作為起始進行向下搜索,當一個對象到GC Roots沒有任何引用鏈(Reference Chain)相連,則證明此對象是不可用的。在Java語言中,GC Roots包括:

1.在VM棧(幀中的本地變量)中的引用

2.方法區中的靜態引用

3.JNI(即一般說的Native方法)中的引用

生存還是死亡?

判定一個對象死亡,至少經歷兩次標記過程:如果對象在進行根搜索后,發現沒有與GC Roots相連接的引用鏈,那它將會被***次標記,并在稍后執行他的finalize()方法(如果它有的話)。這里所謂的“執行”是指虛擬機會觸發這個方法,但并不承諾會等待它運行結束。這點是必須的,否則一個對象在finalize()方法執行緩慢,甚至有死循環什么的將會很容易導致整個系統崩潰。finalize()方法是對象***一次逃脫死亡命運的機會,稍后GC將進行第二次規模稍小的標記,如果在finalize()中對象成功拯救自己(只要重新建立到GC Roots的連接即可,譬如把自己賦值到某個引用上),那在第二次標記時它將被移除出“即將回收”的集合,如果對象這時候還沒有逃脫,那基本上它就真的離死不遠了。

需要特別說明的是,這里對finalize()方法的描述可能帶點悲情的藝術加工,并不代表筆者鼓勵大家去使用這個方法來拯救對象。相反,筆者建議大家盡量避免使用它,這個不是C/C++里面的析構函數,它運行代價高昂,不確定性大,無法保證各個對象的調用順序。需要關閉外部資源之類的事情,基本上它能做的使用try-finally可以做的更好。

關于方法區

方法區即后文提到的***代,很多人認為***代是沒有GC的,在堆中,尤其是在新生代,常規應用進行一次GC可以一般可以回收70%~95%的空間,而***代的GC效率遠小于此。雖然VM Spec不要求,但當前生產中的商業JVM都有實現***代的GC,主要回收兩部分內容:廢棄常量與無用類。這兩點回收思想與Java堆中的對象回收很類似,都是搜索是否存在引用,常量的相對很簡單,與對象類似的判定即可。而類的回收則比較苛刻,需要滿足下面3個條件:

1.該類所有的實例都已經被GC,也就是JVM中不存在該Class的任何實例。

2.加載該類的ClassLoader已經被GC。

3.該類對應的java.lang.Class 對象沒有在任何地方被引用,如不能在任何地方通過反射訪問該類的方法。

是否對類進行回收可使用-XX:+ClassUnloading參數進行控制,還可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載、卸載信息。

在大量使用反射、動態代理、CGLib等bytecode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要JVM具備類卸載的支持以保證***代不會溢出。

垃圾收集算法

在這節里不打算大量討論算法實現,只是簡單的介紹一下基本思想以及發展過程。最基礎的搜集算法是“標記-清除算法”(Mark-Sweep),如它的名字一樣,算法分層“標記”和“清除”兩個階段,首先標記出所有需要回收的對象,然后回收所有需要回收的對象,整個過程其實前一節講對象標記判定的時候已經基本介紹完了。說它是最基礎的收集算法原因是后續的收集算法都是基于這種思路并優化其缺點得到的。它的主要缺點有兩個,一是效率問題,標記和清理兩個過程效率都不高,二是空間問題,標記清理之后會產生大量不連續的內存碎片,空間碎片太多可能會導致后續使用中無法找到足夠的連續內存而提前觸發另一次的垃圾搜集動作。

為了解決效率問題,一種稱為“復制”(Copying)的搜集算法出現,它將可用內存劃分為兩塊,每次只使用其中的一塊,當半區內存用完了,僅將還存活的對象復制到另外一塊上面,然后就把原來整塊內存空間一次過清理掉。這樣使得每次內存回收都是對整個半區的回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存就可以了,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,未免太高了一點。

現在的商業虛擬機中都是用了這一種收集算法來回收新生代,IBM有專門研究表明新生代中的對象98%是朝生夕死的,所以并不需要按照1:1的比例來劃分內存空間,而是將內存分為一塊較大的eden空間和2塊較少的survivor空間,每次使用eden和其中一塊survivor,當回收時將eden和survivor還存活的對象一次過拷貝到另外一塊survivor空間上,然后清理掉eden和用過的survivor。Sun Hotspot虛擬機默認eden和survivor的大小比例是8:1,也就是每次只有10%的內存是“浪費”的。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有10%以內的對象存活,當survivor空間不夠用時,需要依賴其他內存(譬如老年代)進行分配擔保(Handle Promotion)。

復制收集算法在對象存活率高的時候,效率有所下降。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保用于應付半區內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。因此人們提出另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然一樣,但后續步驟不是進行直接清理,而是令所有存活的對象一端移動,然后直接清理掉這端邊界以外的內存。

當前商業虛擬機的垃圾收集都是采用“分代收集”(Generational Collecting)算法,這種算法并沒有什么新的思想出現,只是根據對象不同的存活周期將內存劃分為幾塊。一般是把Java堆分作新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法,譬如新生代每次GC都有大批對象死去,只有少量存活,那就選用復制算法只需要付出少量存活對象的復制成本就可以完成收集。

垃圾收集器

垃圾收集器就是收集算法的具體實現,不同的虛擬機會提供不同的垃圾收集器。并且提供參數供用戶根據自己的應用特點和要求組合各個年代所使用的收集器。本文討論的收集器基于Sun Hotspot虛擬機1.6版。

圖1.Sun JVM1.6的垃圾收集器

JVM內存管理深入垃圾收集器與內存分配策略的示例分析

圖1展示了1.6中提供的6種作用于不同年代的收集器,兩個收集器之間存在連線的話就說明它們可以搭配使用。在介紹著些收集器之前,我們先明確一個觀點:沒有***的收集器,也沒有***的收集器,只有最合適的收集器。

1.Serial收集器

單線程收集器,收集時會暫停所有工作線程(我們將這件事情稱之為Stop The World,下稱STW),使用復制收集算法,虛擬機運行在Client模式時的默認新生代收集器。

2.ParNew收集器

ParNew收集器就是Serial的多線程版本,除了使用多條收集線程外,其余行為包括算法、STW、對象分配規則、回收策略等都與Serial收集器一摸一樣。對應的這種收集器是虛擬機運行在Server模式的默認新生代收集器,在單CPU的環境中,ParNew收集器并不會比Serial收集器有更好的效果。

3.Parallel Scavenge收集器

Parallel Scavenge收集器(下稱PS收集器)也是一個多線程收集器,也是使用復制算法,但它的對象分配規則與回收策略都與ParNew收集器有所不同,它是以吞吐量***化(即GC時間占總運行時間最小)為目標的收集器實現,它允許較長時間的STW換取總吞吐量***化。

4.Serial Old收集器

Serial Old是單線程收集器,使用標記-整理算法,是老年代的收集器,上面三種都是使用在新生代收集器。

5.Parallel Old收集器

老年代版本吞吐量優先收集器,使用多線程和標記-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的話,老年代除Serial Old外別無選擇,因為PS無法與CMS收集器配合工作。

6.CMS(Concurrent Mark Sweep)收集器

CMS是一種以最短停頓時間為目標的收集器,使用CMS并不能達到GC效率***(總體GC時間最小),但它能盡可能降低GC時服務的停頓時間,這一點對于實時或者高交互性應用(譬如證券交易)來說至關重要,這類應用對于長時間STW一般是不可容忍的。CMS收集器使用的是標記-清除算法,也就是說它在運行期間會產生空間碎片,所以虛擬機提供了參數開啟CMS收集結束后再進行一次內存壓縮。

內存分配與回收策略

了解GC其中很重要一點就是了解JVM的內存分配策略:即對象在哪里分配和對象什么時候回關于對象在哪里分配,往大方向講,主要就在堆上分配,但也可能經過JIT進行逃逸分析后進行標量替換拆散為原子類型在棧上分配,也可能分配在DirectMemory中(詳見本文***章)。往細節處講,對象主要分配在新生代eden上,也可能會直接老年代中,分配的細節決定于當前使用的垃圾收集器類型與VM相關參數設置。我們可以通過下面代碼來驗證一下Serial收集器(ParNew收集器的規則與之完全一致)的內存分配和回收的策略。讀者看完Serial收集器的分析后,不妨自己根據JVM參數文檔寫一些程序去實踐一下其它幾種收集器的分配策略。

清單1:內存分配測試代碼

public class YoungGenGC {  private static final int _1MB = 1024 * 1024;  public static void main(String[] args) {   // testAllocation();   testHandlePromotion();   // testPretenureSizeThreshold();   // testTenuringThreshold();   // testTenuringThreshold2();   }  /**   * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8  */   @SuppressWarnings("unused")   public static void testAllocation() {   byte[] allocation1, allocation2, allocation3, allocation4;   allocation1 = new byte[2 * _1MB];   allocation2 = new byte[2 * _1MB];   allocation3 = new byte[2 * _1MB];   allocation4 = new byte[4 * _1MB]; // 出現一次Minor GC   }  /**   * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8  * -XX:PretenureSizeThreshold=3145728   */   @SuppressWarnings("unused")   public static void testPretenureSizeThreshold() {   byte[] allocation;   allocation = new byte[4 * _1MB]; //直接分配在老年代中   }  /**   * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1  * -XX:+PrintTenuringDistribution   */   @SuppressWarnings("unused")   public static void testTenuringThreshold() {   byte[] allocation1, allocation2, allocation3;   allocation1 = new byte[_1MB / 4]; // 什么時候進入老年代決定于XX:MaxTenuringThreshold設置   allocation2 = new byte[4 * _1MB];   allocation3 = new byte[4 * _1MB];   allocation3 = null;   allocation3 = new byte[4 * _1MB];   }  /**   * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15  * -XX:+PrintTenuringDistribution   */   @SuppressWarnings("unused")   public static void testTenuringThreshold2() {   byte[] allocation1, allocation2, allocation3, allocation4;   allocation1 = new byte[_1MB / 4]; // allocation1+allocation2大于survivo空間一半   allocation2 = new byte[_1MB / 4];   allocation3 = new byte[4 * _1MB];   allocation4 = new byte[4 * _1MB];   allocation4 = null;   allocation4 = new byt  /**   * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure  */   @SuppressWarnings("unused")   public static void testHandlePromotion() {   byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;  allocation1 = new byte[2 * _1MB];   allocation2 = new byte[2 * _1MB];   allocation3 = new byte[2 * _1MB];   allocation1 = null;   allocation4 = new byte[2 * _1MB];   allocation5 = new byte[2 * _1MB];   allocation6 = new byte[2 * _1MB];   allocation4 = null;   allocation5 = null;   allocation6 = null;   allocation7 = new byte[2 * _1MB];   }   }

規則一:通常情況下,對象在eden中分配。當eden無法分配時,觸發一次Minor GC。

執行testAllocation()方法后輸出了GC日志以及內存分配狀況。-Xms20M -Xmx20M -Xmn10M這3個參數確定了Java堆大小為20M,不可擴展,其中10M分配給新生代,剩下的10M即為老年代。-XX:SurvivorRatio=8決定了新生代中eden與survivor的空間比例是1:8,從輸出的結果也清晰的看到“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代總可用空間為9216K(eden+1個survivor)。

我們也注意到在執行testAllocation()時出現了一次Minor GC,GC的結果是新生代6651K變為148K,而總占用內存則幾乎沒有減少(因為幾乎沒有可回收的對象)。這次GC是發生的原因是為allocation4分配內存的時候,eden已經被占用了6M,剩余空間已不足分配allocation4所需的4M內存,因此發生Minor GC。GC期間虛擬機發現已有的3個2M大小的對象全部無法放入survivor空間(survivor空間只有1M大小),所以直接轉移到老年代去。GC后4M的allocation4對象分配在eden中。

清單2:testAllocation()方法輸出結果

[GC [DefNew: 6651K->148K(9216K), 0.0070106 secs] 6651K->6292K(19456K), 0.0070426 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  Heap   def new generation total 9216K, used 4326K [0x029d0000, 0x033d0000, 0x033d0000)  eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)   from space 1024K, 14% used [0x032d0000, 0x032f5370, 0x033d0000)   to space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000)   tenured generation total 10240K, used 6144K [0x033d0000, 0x03dd0000, 0x03dd0000)  the space 10240K, 60% used [0x033d0000, 0x039d0030, 0x039d0200, 0x03dd0000)  compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)  the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)  No shared spaces configured.

規則二:配置了PretenureSizeThreshold的情況下,對象大于設置值將直接在老年代分配。

執行testPretenureSizeThreshold()方法后,我們看到eden空間幾乎沒有被使用,而老年代的10M控件被使用了40%,也就是4M的allocation對象直接就分配在老年代中,則是因為PretenureSizeThreshold被設置為3M,因此超過3M的對象都會直接從老年代分配。

清單3:

Heap   def new generation   total 9216K, used 671K [0x029d0000, 0x033d0000, 0x033d0000)     eden space 8192K,   8% used [0x029d0000, 0x02a77e98, 0x031d0000)     from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)     to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)   tenured generation   total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)      the space 10240K,  40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)   compacting perm gen  total 12288K, used 2107K [0x03dd0000, 0x049d0000, 0x07dd0000)      the space 12288K,  17% used [0x03dd0000, 0x03fdefd0, 0x03fdf000, 0x049d0000)   No shared spaces configured.

規則三:在eden經過GC后存活,并且survivor能容納的對象,將移動到survivor空間內,如果對象在survivor中繼續熬過若干次回收(默認為15次)將會被移動到老年代中。回收次數由MaxTenuringThreshold設置。

分別以-XX:MaxTenuringThreshold=1和-XX:MaxTenuringThreshold=15兩種設置來執行testTenuringThreshold(),方法中allocation1對象需要256K內存,survivor空間可以容納。當MaxTenuringThreshold=1時,allocation1對象在第二次GC發生時進入老年代,新生代已使用的內存GC后非常干凈的變成0KB。而MaxTenuringThreshold=15時,第二次GC發生后,allocation1對象則還留在新生代survivor空間,這時候新生代仍然有404KB被占用。

清單4:

MaxTenuringThreshold=1 [GC [DefNew   Desired survivor size 524288 bytes, new threshold 1 (max 1)   - age 1: 414664 bytes, 414664 total   : 4859K->404K(9216K), 0.0065012 secs] 4859K->4500K(19456K), 0.0065283 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]  [GC [DefNew   Desired survivor size 524288 bytes, new threshold 1 (max 1)   : 4500K->0K(9216K), 0.0009253 secs] 8596K->4500K(19456K), 0.0009458 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  Heap   def new generation total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000)  eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)   from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000)   to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)   tenured generation total 10240K, used 4500K [0x033d0000, 0x03dd0000, 0x03dd0000)  the space 10240K, 43% used [0x033d0000, 0x03835348, 0x03835400, 0x03dd0000)  compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)  the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)  No shared spaces configured.  MaxTenuringThreshold=15   [GC [DefNew   Desired survivor size 524288 bytes, new threshold 15 (max 15)   - age 1: 414664 bytes, 414664 total   : 4859K->404K(9216K), 0.0049637 secs] 4859K->4500K(19456K), 0.0049932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  [GC [DefNew   Desired survivor size 524288 bytes, new threshold 15 (max 15)   - age 2: 414520 bytes, 414520 total   : 4500K->404K(9216K), 0.0008091 secs] 8596K->4500K(19456K), 0.0008305 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  Heap   def new generation total 9216K, used 4582K [0x029d0000, 0x033d0000, 0x033d0000)  eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000)   from space 1024K, 39% used [0x031d0000, 0x03235338, 0x032d0000)   to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000)   tenured generation total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000)  the space 10240K, 40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000)  compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)  the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)  No shared spaces configured.

規則四:如果在survivor空間中相同年齡所有對象大小的累計值大于survivor空間的一半,大于或等于個年齡的對象就可以直接進入老年代,無需達到MaxTenuringThreshold中要求的年齡。

執行testTenuringThreshold2()方法,并將設置-XX:MaxTenuringThreshold=15,發現運行結果中survivor占用仍然為0%,而老年代比預期增加了6%,也就是說allocation1、allocation2對象都直接進入了老年代,而沒有等待到15歲的臨界年齡。因為這2個對象加起來已經到達了512K,并且它們是同年的,滿足同年對象達到survivor空間的一半規則。我們只要注釋掉其中一個對象new操作,就會發現另外一個就不會晉升到老年代中去了。

清單5:

[GC [DefNew   Desired survivor size 524288 bytes, new threshold 1 (max 15)   - age   1:     676824 bytes,     676824 total   : 5115K->660K(9216K), 0.0050136 secs] 5115K->4756K(19456K), 0.0050443 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]   [GC [DefNew   Desired survivor size 524288 bytes, new threshold 15 (max 15)   : 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   Heap   def new generation   total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000)     eden space 8192K,  51% used [0x029d0000, 0x02de4828, 0x031d0000)     from space 1024K,   0% used [0x031d0000, 0x031d0000, 0x032d0000)     to   space 1024K,   0% used [0x032d0000, 0x032d0000, 0x033d0000)   tenured generation   total 10240K, used 4756K [0x033d0000, 0x03dd0000, 0x03dd0000)      the space 10240K,  46% used [0x033d0000, 0x038753e8, 0x03875400, 0x03dd0000)   compacting perm gen  total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000)      the space 12288K,  17% used [0x03dd0000, 0x03fe09a0, 0x03fe0a00, 0x049d0000)   No shared spaces configured.

規則五:在Minor GC觸發時,會檢測之前每次晉升到老年代的平均大小是否大于老年代的剩余空間,如果大于,改為直接進行一次Full GC,如果小于則查看HandlePromotionFailure設置看看是否允許擔保失敗,如果允許,那仍然進行Minor GC,如果不允許,則也要改為進行一次Full GC。

前面提到過,新生代才有復制收集算法,但為了內存利用率,只使用其中一個survivor空間來作為輪換備份,因此當出現大量對象在GC后仍然存活的情況(最極端就是GC后所有對象都存活),就需要老年代進行分配擔保,把survivor無法容納的對象直接放入老年代。與生活中貸款擔保類似,老年代要進行這樣的擔保,前提就是老年代本身還有容納這些對象的剩余空間,一共有多少對象在GC之前是無法明確知道的,所以取之前每一次GC晉升到老年代對象容量的平均值與老年代的剩余空間進行比較決定是否進行Full GC來讓老年代騰出更多空間。

取平均值進行比較其實仍然是一種動態概率的手段,也就是說如果某次Minor GC存活后的對象突增,大大高于平均值的話,依然會導致擔保失敗,這樣就只好在失敗后重新進行一次Full GC。雖然擔保失敗時做的繞的圈子是***的,但大部分情況下都還是會將HandlePromotionFailure打開,避免Full GC過于頻繁。

清單6:

HandlePromotionFailure = false [GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]  [GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  HandlePromotionFailure = true [GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  [GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

總  結

介紹了垃圾收集的算法、6款主要的垃圾收集器,以及通過代碼實例具體介紹了新生代串行收集器對內存分配及回收的影響。

GC在很多時候都是系統并發度的決定性因素,虛擬機之所以提供多種不同的收集器,提供大量的調節參數,是因為只有根據實際應用需求、實現方式選擇***的收集方式才能獲取***的性能。沒有固定收集器、參數組合,也沒有***的調優方法,虛擬機也沒有什么必然的行為。筆者看過一些文章,撇開具體場景去談論老年代達到92%會觸發Full GC(92%應當來自CMS收集器觸發的默認臨界點)、98%時間在進行垃圾收集系統會拋出OOM異常(98%應該來自parallel收集器收集時間比率的默認臨界點)其實意義并不太大。因此學習GC如果要到實踐調優階段,必須了解每個具體收集器的行為、優勢劣勢、調節參數。

關于JVM內存管理深入垃圾收集器與內存分配策略的示例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

jvm
AI

鄂托克旗| 景德镇市| 花莲县| 平江县| 二连浩特市| 莒南县| 新邵县| 桑植县| 珲春市| 右玉县| 德钦县| 德州市| 英吉沙县| 阳西县| 无锡市| 永靖县| 大庆市| 涟源市| 睢宁县| 苍山县| 永寿县| 澄迈县| 南昌市| 黄骅市| 双鸭山市| 越西县| 阜平县| 铜陵市| 聂拉木县| 石泉县| 昌吉市| 昌宁县| 万山特区| 荥经县| 杭州市| 石家庄市| 正蓝旗| 那坡县| 蛟河市| 定西市| 绵竹市|