天天看點

JVM記憶體管理之GC垃圾回收體系

從四則運算引發對JVM的認識之後,對象進行垃圾收集?如何去進行收集?

>>>https://blog.csdn.net/yxd179/article/details/84189340?yxd179

First、确定對象是否需要回收:引用計數法、可達性算法。

引用計數法簡述:給對象添加一個引用計數器,若引用則計數器加一,引用失效則減一,計數器為零的對象就是不再被使用的。(其中兩個或多個對象之間可能會互相引用對方,導緻這樣的對象無法被回收。)

可達性算法簡述:在一系列的稱為"GC Roots"的對象作為start node,從這些node開始向下搜尋,搜尋所走過的路徑稱為Reference Chain引用鍊,當一個對象到GC Roots沒有任何引用鍊相連時,則說明此對象是不可用的。(其中虛拟機棧<棧幀中的本地變量表>中引用對象、方法區中的類靜态屬性引用的對象、方法區中常量引用的對象<final常量值>、本地方法棧JNI<Native方法>引用的對象可視為GC root的對象,解決了引用計數法的問題。)

Second、GC主要是針對堆的操作:标記清除、複制算法、标記整理<老年代-主要是把存活對象移到記憶體的一端>、分代收集<對新生代采用複制算法,對老年代采用标記整理或者标記清除算法>。

垃圾收集器簡述>>>

1、串行收集器(Serial Collector,複制算法)>>>新生代單線程收集器(标記和清除都是單線程),是client模式預設的GC,簡單高效,也可通過-XX:+UseSerialGC強制指定别的GC方式;

2、串行老年代收集器(Serial Old Collector,清除-标記算法)>>>老年代單線程收集器;

并行新生代收集器(ParNew Collector,停止-複制算法)>>>新生代并行收集器,在多核CPU環境下,比串行收集器效率更高;

3、Parallel Scavenge收集器(停止-複制算法)>>>并行收集器,高效利用CPU以獲得高吞吐量(使用者線程時間/<使用者線程時間+GC線程時間>),server模式預設采用的GC方式;可用-XX:+UseParallelGC和-XX:ParallelGCThreads指定GC方式和GC線程數;

4、Parallel Old Scavenge收集器(停止-複制算法)>>>老年代并行收集器(吞吐量優先);

5、CMS收集器(Concurrent Mark Sweep,标記清理算法)>>>高并發,低停頓(GC回收時暫停應用),響應快,尤适合多CPU。

在圖示Eden區域:棧8份,兩個survivor區域各占1份,即8:1:1(新生代中98%的對象"朝生夕滅",很少會存活下來,是以就設定了10%的空間來存放活下來的,詳細日志-XX:+PrintGCDetails、比例-XX:SurvivorRatio=8、Xms20M堆記憶體最小值、-Xmx20M堆記憶體最大值、-Xmn10M堆記憶體配置設定給新生代、-XX:PermSize設定持久代堆空間的初始值和最小值、-XX:MaxPermSize=<n>[g|m|k]設定持久代堆空間的最大值)。大多數的新生代都是采用的複制清除法作為垃圾回收算法。當對新生代進行minor gc(發生在新生代的垃圾收集動作,java對象大多都具備"朝生夕滅"的特性,是以Minor GC非常頻繁,一般回收速度也比較快)時,會把Eden中和Survivor中的存活對象複制到另一塊survivor區域中。)

标記-清除:接标記清除即可,不足的是效率不高,空間會産生大量碎片。

複制:将空間分成兩塊,每次隻對其中一塊進行 GC。若這塊記憶體使用完時,将還存活的對象複制到另一塊上面去。不足的是會造成空間使用率低下。因為大多數新生代對象都不會熬過第一次 GC,沒有必要配置設定1:1的劃分空間,如圖,可分一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 空間和其中一塊 Survivor。當回收的時候,将 Eden 和 Survivor 中還存活的對象一次性複制到另一塊 Survivor 上,最後去清理 Eden 和 Survivor 空間。比例一般是 8 : 1 : 1,但每次浪費 10% 的 Survivor 空間。

新生代每次垃圾回收都有大量對象死去,隻有少量存活,選用複制算法比較合理,而老年代中對象存活率較高、沒有額外的空間配置設定對它進行擔保,使用标記清除或标記整理算法回收。

JVM記憶體管理之GC垃圾回收體系

若是出現了不止10%的對象存活下來呢?多出來的對象直接進入老年代

則從新生代晉升到老年代:對象熬過一次GC,年齡加一(MaxTenuringThreshold),當達到設定的門檻值,則可進入老年代;比較大的對象(需要大量連續的記憶體空間PretenureSizeThreshold,預設15),當對象大于這個值,則可直接進入老年代;Survivor空間中相同年齡的對象大小總和大于Survivor空間的一半(即上述情況),則年齡大于或等于該年齡的對象就可以進入老年代。(動态判定,适應那些記憶體較小的情況,survivor from和 survivor to之間不停互換角色。)

那GC是否可直接記憶體嚒?why not?(Direct Memory:系統實體記憶體,不是虛拟機運作時資料區的一部分,也不是Java虛拟機規範中定義的記憶體區域,但是這部分記憶體也被頻繁地使用,而且也可能導緻OutOfMemoryError異常出現。在jdk1.4中新加入了NIO類,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,可以使用Native函數庫直接配置設定堆外記憶體,然後通過一個存儲在Java堆裡面的DirectByteBuffer對象作為這塊記憶體的引用進行操作,避免了在Java堆和Native堆中來回複制資料。)

HotSpot的算法:在實際項目中,方法區的内容可能會非常多,引用數量龐大,運作中的java程式的引用情況都是在發生變化的,是以在進行可達性分析的時候,對象引用的關系在某個時間段上,必須是保持不變的,這一點是導緻進行GC進行時必須停頓所有java執行線程的一個重要原因——"Stop the world".(Hotspot虛拟機中使用一組稱為OopMap的資料結構來維護哪些地方存放着對象引用,Hotspot虛拟機可以快速且準确地完成GC Roots枚舉,而選擇恰當的時機進行GC仍然關鍵,這些特定的GC位置被稱為SafePoint<是否具有讓程式長時間執行的特點。比較典型的如方法調用,循環跳轉等都是safepoint的選擇>、安全區域<指線程進入到某個區域時就可以被JVM的GC直接忽略。典型的應用場景是線程處于Sleep或者Blocked狀态的場景>。)

Dump檔案分析>>>https://blog.csdn.net/yxd179/article/details/83307567?yxd179

觸發full gc:System.gc(),Runtime.getRuntime().gc(),java.lang.management.MemoryMXBean.gc()、老年代空間不足、Permanet Generation空間滿、CMS GC時出現promotion failed和concurrent mode failure、Minor GC晉升到舊生代的平均大小大于舊生代的剩餘空間。

老年代空間不足(在新生代對象轉入及建立為大對象、大數組時才會出現不足的現象,執行Full GC後空間仍然不足,抛出java.lang.OutOfMemoryError: Java heap space,為避免以上兩種狀況引起的FullGC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。)

Permanet Generation(存放class資訊等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執行Full GC。若通過Full GC仍回收不了,出現java.lang.OutOfMemoryError: PermGen space,為避免Perm Gen占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。)

CMS GC時出現promotion failed和concurrent mode failure(對于采用CMS進行舊生代GC的程式而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。 promotionfailed是在進行Minor GC時,survivor space放不下、對象隻能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。 可增大survivorspace、舊生代空間或調低觸發并發GC的比率,根據jdk的不同版本,若CMS在remark完畢後很久才觸發sweeping動作,可設定-XX:CMSMaxAbortablePrecleanTime=5。)

Minor GC晉升到舊生代的平均大小大于舊生代的剩餘空間(Hotspot為了避免由于新生代對象晉升到舊生代導緻舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩餘空間,那麼就直接觸發Full GC。 例如程式第一次觸發MinorGC後,有6MB的對象晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大于6MB,若小于6MB,則執行Full GC。 當新生代采用PSGC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大于6MB,如小于,則觸發對舊生代的回收。 除了以上4種狀況外,對于使用RMI來進行RPC或管理的Sun JDK應用而言,預設情況下會一小時執行一次Full GC。可通過在啟動時通過- java-Dsun.rmi.dgc.client.gcInterval=3600000來設定Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc()。)

>>>附達人整理的牛客鵝廠、螞蟻等大廠面經?

https://www.nowcoder.com/discuss/120239?order=4&pos=23&type=0

>>>附達人整理的JVM類加載機制csdn?

https://blog.csdn.net/topdeveloperr/article/details/80816309#關于OSGI

>>>深入了解Java虛拟機JVM進階特性與最佳實踐第2版.pdf?

https://uploadfiles.nowcoder.com/learning_material/20180810/3353131_1533908669399_%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BAJVM%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E7%AC%AC2%E7%89%88.pdf