目錄java
程序的運行必然須要申請內存資源,無效的對象資源若是不及時處理就會一直佔有內存 資源,最終將致使內存溢出,因此對內存資源的管理是很是重要了。
C/C++語言的垃圾回收 在C/C++語言中,沒有自動垃圾回收機制,是經過new關鍵字申請內存資源,經過delete 關鍵字釋放內存資源。 若是,程序員在某些位置沒有寫delete進行釋放,那麼申請的對象將一直佔用內存資源, 最終可能會致使內存泄露。
Java語言的垃圾回收 爲了讓程序員更專一於代碼的實現,而不用過多的考慮內存釋放的問題,因此,在Java語 言中,有了自動的垃圾回收機制,也就是咱們熟悉的GC(Garbage Collection)。 有了垃圾回收機制後,程序員只須要關心內存的申請便可,內存的釋放由系統自動識別 完成。 換句話說,自動的垃圾回收的算法就會變得很是重要了,若是由於算法的不合理,致使 內存資源一直沒有釋放,一樣也可能會致使內存泄露的。git
對象在Eden Space建立,當Eden Space滿了的時候,gc就把全部在Eden Space中的對象掃描一次,把全部有效的對象複製到第一個Survivor Space,同時把無效的對象所佔用的空間釋放。當Eden Space再次變滿了的時候,就啓動移動程序把Eden Space中有效的對象複製到第二個Survivor Space,同時,也將第一個Survivor Space中的有效對象複製到第二個Survivor Space。若是填充到第二個Survivor Space中的有效對象被第一個Survivor Space或Eden Space中的對象引用,那麼這些對象就是長期存在的,此時這些對象將被複制到Permanent Generation。若垃圾收集器依據這種小幅度的調整收集不能騰出足夠的空間,就會運行Full GC,此時JVM GC中止全部在堆中運行的線程並執行清除動做。程序員
自動化的管理內存資源,垃圾回收機制必需要有一套算法來進行計算,哪些是有效的對 象,哪些是無效的對象,對於無效的對象就要進行回收處理。 常見的垃圾回收算法有:引用計數法、標記清除法、標記壓縮法、複製算法、分代算法 等。github
假設有一個對象A,任何一個對象對A的引用,那麼對象A的引用計數器+1,當引用失敗 時,對象A的引用計數器就-1,若是對象A的計數器的值爲0,就說明對象A沒有引用了, 能夠被回收web
以下所示,即便a和b都爲null,可是因爲a和b存在循環引用,這樣a和b永遠都不會被回收算法
class TestA{ public TestB b; } class TestB{ public TestA a; } public class Main{ public static void main(String[] args){ A a = new A(); B b = new B(); a.b=b; b.a=a; a = null; b = null; } }
標記清除算法主要就是爲了解決引用計數法沒法解決的循環引用問題。標記清除法將垃圾回收分爲2個階段,分別是標記和清除。
-- 標記:從根節點開始標記引用的對象。
-- 清除:未被標記引用的對象就是垃圾對象,能夠被清理服務器
照根搜索算法,全部從root對象可達的對象就被標記爲了存活的對象,此 時已經完成了第一階段標記。接下來,就要執行第二階段清除併發
接下來,喚醒中止的程序線程,讓程序繼續運行oracle
優勢
標記清除算法解決了引用計數算法中的循環引用的問題,沒有從root節點引 用的對象都會被回收dom
缺點
-- 效率較低,標記和清除兩個動做都須要遍歷全部的對象,而且在GC時,須要中止應 用程序,對於交互性要求比較高的應用而言這個體驗是很是差的。
-- 碎片化,經過標記清除算法清理出來的內存,碎片化較爲嚴重,由於被回收的對象可能存在於 內存的各個角落,因此清理出來的內存是不連貫的。
標記清楚法,有個很明顯的肯定就是清楚後的內存碎片化嚴重,而標記壓縮算法是在標記清除算法的基礎之上,作了優化改進的算法。和標記清除算法一 樣,也是從根節點開始,對對象的引用進行標記,在清理階段,並非簡單的清理未標 記的對象,而是將存活的對象壓縮到內存的一端,而後清理邊界之外的垃圾,從而解決 了碎片化的問題。
複製算法的核心就是,將原有的內存空間一分爲二,每次只用其中的一塊,在垃圾回收 時,將正在使用的對象複製到另外一個內存空間中,而後將該內存空間清空,交換兩個內存的角色,完成垃圾的回收。 若是內存中的垃圾對象較多,須要複製的對象就較少,這種狀況下適合使用該方式而且 效率比較高,反之,則不適合
在JVM年輕代內存空間使用的就是複製算法
前面說了4種回收算法,每一種算法都有優缺點,在實際的JVM垃圾回收中,就採用了分代算法來處理實際的垃圾回收,即根據JVM的內存分區採起不一樣的垃圾回收算法。
年輕代:
年輕代存放的是新建立的對象,內存空間相對小垃圾回收頻繁,大多數對象存在時間短朝生夕死,因此採用的是複製算法,來做垃圾回收
年老代:
年老代存放的是JVM認爲生命週期比較長的對象,即經在年輕代通過垃圾回收仍然存在的對象會被放入年老代,同時年老代的內存空間也相對更大,GC沒有年輕代頻繁。年老代主要採用標記清楚法或壓縮標記法來避免內存碎片。
前面說了垃圾回收的算法,但還須要有具體的實現,在jvm中,實現了多種垃圾收集器,包括:串行垃圾收集器、並行垃圾收集器、CMS(併發)垃圾收集器、G1垃圾收集器,
使用單線程進行垃圾回收,垃圾回收時,只有一個線程在工做, 而且java應用中的全部線程都要暫停,等待垃圾回收的完成。這種現象稱之爲 STW(Stop-The-World)。 對於交互性較強的應用而言,這種垃圾收集器是不可以接受的。 通常在Javaweb應用中是不會採用該收集器的。
package JavaCore.JVM.GC; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Random; /******************************************************************************* * @Copyright (C), 2018-2019,github:Swagger-Ranger * @FileName: TestGC * @Author: liufei32@outlook.com * @Date: 2019/4/11 15:08 * @Description: 測試串行GC,查看GC過程 * @Aha-eureka: IDEA配置運行參數VM options:-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m * -XX:+UseSerialGC 使用串行垃圾回收 * -XX:+PrintGCDetails 打印垃圾回收信息 * -Xms16m -Xmx16m 設置VM啓動內存大小和最大內存大小 * * * [0.009s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead. * [0.022s][info ][gc] Using Serial * [0.022s][info ][gc,heap,coops] Heap address: 0x00000000ff000000, size: 16 MB, Compressed Oops mode: 32-bit * [0.308s][info ][gc,start ] GC(0) Pause Young (Allocation Failure) <---- Pause Young (Allocation Failure) 開始年輕代GC,不包括年老代,GC緣由Allocation Failure內存分配失敗,即內存耗光,自己設置16M,年輕代分配的確定就更小 * [0.316s][info ][gc,heap ] GC(0) DefNew: 4416K->512K(4928K) <---- DefNew:開始串行回收,4416k開始回收時佔用內存,512k回收後佔用內存,4928k總共內存 * [0.316s][info ][gc,heap ] GC(0) Tenured: 0K->2100K(10944K) <---- 能夠看出回收前佔用0,回收後佔用2100k,有年輕代的對象被放入了年老代 * [0.316s][info ][gc,metaspace ] GC(0) Metaspace: 6580K->6580K(1056768K) <---- 在這個GC過程當中沒有動方法區中的對象 * [0.316s][info ][gc ] GC(0) Pause Young (Allocation Failure) 4M->2M(15M) 8.220ms * [0.317s][info ][gc,cpu ] GC(0) User=0.02s Sys=0.00s Real=0.01s * [0.340s][info ][gc,start ] GC(1) Pause Young (Allocation Failure) * [0.349s][info ][gc,heap ] GC(1) DefNew: 4928K->512K(4928K) * [0.349s][info ][gc,heap ] GC(1) Tenured: 2100K->5016K(10944K) * [0.349s][info ][gc,metaspace ] GC(1) Metaspace: 6580K->6580K(1056768K) * [0.349s][info ][gc ] GC(1) Pause Young (Allocation Failure) 6M->5M(15M) 8.863ms * [0.349s][info ][gc,cpu ] GC(1) User=0.02s Sys=0.00s Real=0.01s * [0.454s][info ][gc,start ] GC(2) Pause Young (Allocation Failure) * [0.460s][info ][gc,heap ] GC(2) DefNew: 4928K->512K(4928K) * [0.460s][info ][gc,heap ] GC(2) Tenured: 5016K->7894K(10944K) * [0.460s][info ][gc,metaspace ] GC(2) Metaspace: 6580K->6580K(1056768K) * [0.460s][info ][gc ] GC(2) Pause Young (Allocation Failure) 9M->8M(15M) 6.236ms * [0.460s][info ][gc,cpu ] GC(2) User=0.02s Sys=0.00s Real=0.01s * ...... * [0.904s][info ][gc,cpu ] GC(7) User=0.00s Sys=0.00s Real=0.00s * [1.057s][info ][gc,start ] GC(8) Pause Young (Allocation Failure) * [1.057s][info ][gc,start ] GC(9) Pause Full (Allocation Failure) <----Pause Full (Allocation Failure) 內存分配失敗,全部內存空間開始所有GC,即包括老年代 * [1.057s][info ][gc,phases,start] GC(9) Phase 1: Mark live objects <----能夠看出採用了壓縮標記法,先標記存活的對象 * [1.061s][info ][gc,phases ] GC(9) Phase 1: Mark live objects 3.279ms * [1.061s][info ][gc,phases,start] GC(9) Phase 2: Compute new object addresses <----計算內存大小 * [1.064s][info ][gc,phases ] GC(9) Phase 2: Compute new object addresses 3.114ms * [1.064s][info ][gc,phases,start] GC(9) Phase 3: Adjust pointers <----調整內存引用 * [1.066s][info ][gc,phases ] GC(9) Phase 3: Adjust pointers 2.074ms * [1.066s][info ][gc,phases,start] GC(9) Phase 4: Move objects <----移動對象 * [1.067s][info ][gc,phases ] GC(9) Phase 4: Move objects 0.777ms * [1.067s][info ][gc ] GC(9) Pause Full (Allocation Failure) 13M->3M(15M) 9.637ms * [1.067s][info ][gc,heap ] GC(8) DefNew: 4928K->0K(4928K) * [1.067s][info ][gc,heap ] GC(8) Tenured: 8674K->3340K(10944K) * ...... * [2.327s][info ][gc ] GC(16) Pause Young (Allocation Failure) 8M->6M(15M) 3.350ms * [2.327s][info ][gc,cpu ] GC(16) User=0.00s Sys=0.00s Real=0.00s * [2.537s][info ][gc,start ] GC(17) Pause Young (Allocation Failure) * [2.547s][info ][gc,heap ] GC(17) DefNew: 4928K->511K(4928K) * [2.547s][info ][gc,heap ] GC(17) Tenured: 6636K->8563K(10944K) * [2.547s][info ][gc,metaspace ] GC(17) Metaspace: 7493K->7493K(1056768K) <-------注意:Metaspace方法區的內存空間幾乎一致沒變,由於其存放的是類的結果,不是對象,且其空間在JDK1.8以後也不在VM內存中而是在服務器內存中,其內存大小是能夠動態變化的 * ...... *******************************************************************************/ public class TestGC { public static void main( String[] args ) { serialGC(); } public static void serialGC() { List<Object> list = new ArrayList<>(); while (true) { int sleep = new Random().nextInt(100); if (System.currentTimeMillis() % 2 == 0) { list.clear(); } else { for (int i = 0; i < 10000; i++) { Properties properties = new Properties(); properties.put("key_" + i, "value_" + System.currentTimeMillis() + i); list.add(properties); } } try { Thread.sleep(sleep); } catch (InterruptedException e) { e.printStackTrace(); } } } }
ParNew垃圾收集器是工做在年輕代上的,只是將串行的垃圾收集器改成了並,而,老年代使用的依然是串行 收集器。
-XX:+UseParNewGC 參數設置年輕代使用ParNew回收器
注意在不一樣的JVM中,可能會有不一樣的垃圾回收器,全部有些參數可能在你的JVM中就沒法使用
-XX:UseAdaptiveSizePolicy 自適應GC模式,垃圾回收器將自動調全年輕代、老年代等參數,達到吞吐量、 堆大小、停頓時間之間的平衡。 通常用於,手動調整參數比較困難的場景,讓收集器自動進行調整。
‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐XX:MaxGCPauseMillis=100 ‐ XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
CMS全稱 Concurrent Mark Sweep,是一款併發的、使用標記-清除算法的垃圾回收器, 該回收器是針對老年代垃圾回收的,經過參數-XX:+UseConcMarkSweepGC進行設置
執行過程:
-- 初始化標記(CMS-initial-mark) ,標記root,會致使stw;
-- 併發標記(CMS-concurrent-mark),與用戶線程同時運行;
-- 預清理(CMS-concurrent-preclean),與用戶線程同時運行;
-- 從新標記(CMS-remark) ,會致使stw;
-- 併發清除(CMS-concurrent-sweep),與用戶線程同時運行;
-- 調整堆大小,設置CMS在清理以後進行內存壓縮,目的是清理內存中的碎片;
-- 併發重置狀態等待下次CMS的觸發(CMS-concurrent-reset),與用戶線程同時運行;
啓動參數:
-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m
package JavaCore.JVM.GC; /******************************************************************************* * @Copyright (C), 2018-2019,github:Swagger-Ranger * @FileName: TestGC * @Author: liufei32@outlook.com * @Date: 2019/4/11 15:08 * @Description: 測試CMS GC,查看GC過程 * @Aha-eureka: IDEA配置運行參數VM options:-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m * * CMS將在JDK1.9以後移除,使用-Xlog:gc*來代替 *Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release. * [0.009s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead. * *[0.518s][info ][gc,cpu ] GC(7) User=0.03s Sys=0.00s Real=0.01s * [0.518s][info ][gc,start ] GC(8) Pause Initial Mark <----初始標記 * [0.519s][info ][gc ] GC(8) Pause Initial Mark 7M->7M(15M) 0.409ms * [0.519s][info ][gc,cpu ] GC(8) User=0.00s Sys=0.00s Real=0.00s * [0.519s][info ][gc ] GC(8) Concurrent Mark <---- 併發標記 * [0.528s][info ][gc ] GC(8) Concurrent Mark 8.778ms * [0.528s][info ][gc,cpu ] GC(8) User=0.00s Sys=0.00s Real=0.01s * [0.528s][info ][gc ] GC(8) Concurrent Preclean <----預處理 * [0.528s][info ][gc ] GC(8) Concurrent Preclean 0.072ms * [0.528s][info ][gc,cpu ] GC(8) User=0.00s Sys=0.00s Real=0.00s * [0.528s][info ][gc,start ] GC(8) Pause Remark <----從新標記 * [0.530s][info ][gc ] GC(8) Pause Remark 8M->8M(15M) 1.609ms * [0.530s][info ][gc,cpu ] GC(8) User=0.02s Sys=0.00s Real=0.00s * [0.530s][info ][gc ] GC(8) Concurrent Sweep <----併發清除 * [0.533s][info ][gc ] GC(8) Concurrent Sweep 3.021ms * [0.533s][info ][gc,cpu ] GC(8) User=0.00s Sys=0.00s Real=0.00s * [0.533s][info ][gc ] GC(8) Concurrent Reset <----重置 * [0.533s][info ][gc ] GC(8) Concurrent Reset 0.038ms * [0.533s][info ][gc,cpu ] GC(8) User=0.00s Sys=0.00s Real=0.00s * [0.533s][info ][gc,heap ] GC(8) Old: 7016K->7016K(10944K) * [0.599s][info ][gc,start ] GC(9) Pause Young (Allocation Failure) *******************************************************************************/ public class TestGC_CMS { public static void main( String[] args ) { TestGC.serialGC(); } }
G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方計劃在jdk9中將 G1變成默認的垃圾收集器,以替代CMS。
G1的設計原則就是簡化JVM性能調優,開發人員只須要簡單的三步便可完成調優:
G1中提供了三種模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不一樣的條件 下被觸發
G1垃圾收集器相對比其餘收集器而言,最大的區別在於它取消了年輕代、老年代的物理 劃分,取而代之的是將堆劃分爲若干個區域(Region),這些區域中包含了有邏輯上的 年輕代、老年代區域。 這樣作的好處就是,咱們不再用單獨的空間對每一個代進行設置了,不用擔憂每一個代內 存是否足夠。
G1中的E,S,O均可以和以前的內存物理分區對應,但新增了一個Humongous區即矩形對象區:若是一個對象佔用的空間超過了分區容量50%以上就是巨型對象。
在G1垃圾收集器中,垃圾收集的方式是將存活的對象直接拷貝到老年代或者S區,以前的那個佔用內存空間就空出來,這樣就完成了清理工做。這是一種更加簡單有效的垃圾收集方式,但假設有獨享佔用了分區容量的50%那這個對象就沒法按照上面的那種直接拷貝的方法來回收內存。同時以前在處理大對象時的方法默認直接會被分配在老年代,可是若是它是一個短時間存在的巨型對象,就會對垃圾收集器形成負面影響。爲了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象。若是 一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區來存儲。爲了能找到連續 的H區,有時候不得不啓動Full GC。
再次溫習下,在G1GC中是取消了物理上的內存年輕代、年老代的劃分而是將堆內存劃分爲若干個區域,在這些若干的區域裏採用了邏輯上的年輕代和年老代的劃分也就是說每一塊的物理內存區域在邏輯上是年輕代、年老代或是屬於E,S仍是O區是能夠變化的。
Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發,具體的過程:
-- 在YoungGC,是會STW(Stop-The-World)的,即應用中止等待GC完成
-- Eden空間的數據移動到Survivor空間中,若是Survivor空間不夠,Eden空間的部分 數據會直接晉升到年老代空間。 邏輯上的分區是能夠變的,即內存實際並無改變而是直接在邏輯上變動分區
-- Survivor區的數據移動到新的Survivor區中,也有部分數據晉升到老年代空間中。 最終Eden空間的數據爲空,GC中止工做,應用線程繼續執行
在垃圾回收時,咱們確認哪些對象能夠被垃圾回收,去標記須要回收的對象以釋放內存都是從root對象開始,但有個重要的問題一直沒有討論就是哪些對象是root對象。
在GC年輕代的對象時,咱們如何找到年輕代中對象的根對象呢? 根對象多是在年輕代中,也能夠在老年代中,那麼老年代中的全部對象都是根麼? 若是全量掃描老年代,那麼這樣掃描下來會耗費大量的時間。 因而,G1引進了RSet的概念。它的全稱是Remembered Set,其做用是跟蹤指向某個堆 內的對象引用。
每一個Region初始化時,會初始化一個RSet,該集合用來記錄並跟蹤其它Region指向該 Region中對象的引用,每一個Region默認按照512Kb劃分紅多個Card,因此RSet須要記錄 的東西就是 xx Region的 xx Card。這樣在確認回收對象時就只須要去遍歷Rset集合就能夠了而不用去掃描每一個對象。
當愈來愈多的對象晉升到老年代old region時,爲了不堆內存被耗盡,虛擬機會觸發一 個混合的垃圾收集器,即Mixed GC,該算法並非一個Old GC,除了回收整個Young Region,還會回收一部分的Old Region,這裏須要注意:是一部分老年代,而不是所有 老年代,能夠選擇哪些old region進行收集,從而能夠對垃圾回收的耗時時間進行控制。 也要注意的是Mixed GC 並非 Full GC。 MixedGC何時觸發? 由參數 -XX:InitiatingHeapOccupancyPercent=n 決定。默 認:45%,該參數的意思是:當老年代大小佔整個堆大小百分比達到該閥值時觸發。
-XX:+UseG1GC 使用 G1 垃圾收集器
-XX:MaxGCPauseMillis 設置指望達到的最大GC停頓時間指標(JVM會盡力實現,但不保證達到),默認 值是 200 毫秒。
-XX:G1HeapRegionSize=n 設置的 G1 區域的大小。值是 2 的冪,範圍是 1 MB 到 32 MB 之間。目標是根 據最小的 Java 堆大小劃分出約 2048 個區域。 默認是堆內存的1/2000。
-XX:ParallelGCThreads=n 設置 STW 工做線程數的值。將 n 的值設置爲邏輯處理器的數量。n 的值與邏輯 處理器的數量相同,最多爲 8。
-XX:ConcGCThreads=n設置並行標記的線程數。將 n 設置爲並行垃圾回收線程數 (ParallelGCThreads) 的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=n 設置觸發標記週期的 Java 堆佔用率閾值。默認佔用率是整個 Java 堆的 45%。
前面經過-XX:+PrintGCDetails能夠對GC日誌進行打印,咱們就能夠在控制檯查看,這樣 雖然能夠查看GC的信息,可是並不直觀,能夠藉助於第三方的GC日誌分析工具進行查看。
GC Easy是一款在線的可視化工具,易用、功能強大。
地址:
http://gceasy.io/
在運行JVM前設置參數,並將JVM GC日誌輸出到log文件,而後將文件上傳到gceasy.io在線分析工具中
‐XX:+PrintGC 輸出GC日誌 ‐XX:+PrintGCDetails 輸出GC的詳細日誌 ‐XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式) ‐XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013‐05‐ 04T21:53:59.234+0800) ‐XX:+PrintHeapAtGC 在進行GC的先後打印出堆的信息 ‐Xloggc:../logs/gc.log 日誌文件的輸出路徑
VM options:----具體的參數不一樣的Jdk版本可能有區別
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails - XX:+PrintGCTimeStamps ‐-X:+PrintGCDateStamps -X:+PrintHeapAtGC -Xloggc:D://Swagger-Ranger//test//gc.log
解析後就能夠看到,好比:
https://gceasy.io/my-gc-report.jsp?p=c2hhcmVkLzIwMTkvMDQvMTEvLS1nYy5sb2ctLTE2LTE0LTM5&channel=WEB
其中對應的配置爲-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails -Xloggc:D://Swagger-Ranger//gc.log
本博客爲Swagger-Ranger的筆記分享,文章會持續更新 文中源碼地址: https://github.com/Swagger-Ranger 歡迎交流指正,若有侵權請聯繫做者確認刪除: liufei32@outlook.com