原作者 https://smartan123.github.io/book/?file=home-%E9%A6%96%E9%A1%B5
怕丟失做CV拷貝處理
慕課網(wǎng)課程地址: https://coding.imooc.com/class/chapter/442.html#Anchor
請多多支持正版
一、什么是垃圾回收?
程序的運行必然需要申請內存資源,無效的對象資源如果不及時處理就會一直占有內存資源,最終將導致內存溢出,所以對內存資源的管理是非常重要了,對無效對象的內存回收就叫做垃圾回收。
1、c/c++語言中的垃圾回收
在C/C++語言中,沒有自動垃圾回收機制,是通過new關鍵字申請內存資源,通過delete關鍵字釋放內存資源。
如果,程序員在某些位置沒有寫delete進行釋放,那么申請的對象將一直占用內存資源,最終可能會導致內存溢出 。
2、java語言中的垃圾回收
為了讓程序員更專注于代碼的實現(xiàn),而不用過多的考慮內存釋放的問題,所以,在Java語言中,有了自動的垃圾回收機制,也就是我們熟悉的GC。
有了垃圾回收機制后,程序員只需要關心內存的申請即可,內存的釋放由系統(tǒng)自動識別完成。
換句話說,自動的垃圾回收的算法就會變得非常重要了,如果因為算法的不合理,導致內存資源一直沒有釋放,同樣也可能會導致內存溢出的。
當然,除了Java語言,C#、Python等語言也都有自動的垃圾回收機制。
二、垃圾回收的常見算法
自動化的管理內存資源,垃圾回收機制必須要有一套算法來進行計算,哪些是有效的對象,哪些是無效的對象,對于無效的對象就要進行回收處理。
常見的垃圾回收算法有:引用計數(shù)法、標記清除法、標記壓縮法、復制算法、分代算法等。
1、引用計數(shù)法
引用計數(shù)是歷史最悠久的一種算法,最早George E. Collins在1960的時候首次提出,60年后的今天,該算法依然被很多編程語言使用。
1)、原理
假設有一個對象A,任何一個對象對A的引用,那么對象A的 引用計數(shù)器+1,當引用失敗時,對象A的引用計數(shù)器就-1,如果對象A的計數(shù)器的值為0,就說明對象A沒有引用了,可以被回收。
2)、優(yōu)缺點
優(yōu)點:
- 實時性較高,無需等到內存不夠的時候,才開始回收,運行時根據(jù)對象的計數(shù)器是否為0,就可以直接回收。
- 在垃圾回收過程中,應用無需掛起。如果申請內存時,內存不足,則立刻報outofmemory 錯誤。
- 區(qū)域性,更新對象的計數(shù)器時,只是影響到該對象,不會掃描全部對象。
缺點:
- 每次對象被引用時,都需要去更新計數(shù)器,有一點時間開銷。
- 浪費CPU資源,即使內存夠用,仍然在運行時進行計數(shù)器的統(tǒng)計。
- 無法解決循環(huán)引用問題。(最大的缺點)
什么是循環(huán)引用?

具體講解請觀看視頻
2、標記清除法
標記清除算法是將垃圾回收分為2個階段,分別是標記和清除。
標記:從根節(jié)點開始標記引用的對象
清除:未被標記引用的對象就是垃圾對象,可以被清理
1)、原理

上面這張圖代表的是程序運行期間所有對象的狀態(tài),它們的標志位全部是0(也就是未標記,以下默認0就是未標記,1為已標記),假設這會兒有效內存空間耗盡了,JVM將會停止應用程序的運行并開啟GC線程,然后開始進行標記工作,按照根搜索算法,標記完以后,所有從root對象可達的對象就被標記為了存活的對象,此時已經(jīng)完成了第一階段標記。接下來,就要執(zhí)行第二階段清除了,那么清除完以后,剩下的對象以及對象的狀態(tài)如下圖所示 :

可以看到,沒有被標記的對象將會回收清除掉,而被標記的對象將會留下,并且會將標記位重新歸0。接下來就不用說了,喚醒停止的程序線程,讓程序繼續(xù)運行即可。
2)、優(yōu)缺點
優(yōu)點:
- 標記清除算法解決了引用計數(shù)算法中的循環(huán)引用的問題,沒有從root節(jié)點引用的對象都會被回收。
缺點:
- 效率較低,標記和清除兩個動作都需要遍歷所有的對象,并且在GC時,需要停止應用程序,對于交互性要求比較高的應用而言這個體驗是非常差的。
- 通過標記清除算法清理出來的內存,碎片化較為嚴重,因為被回收的對象可能存在于內存的各個角落,所以清理出來的內存是不連貫的。
3、標記壓縮法
標記壓縮算法是在標記清除算法的基礎之上,做了優(yōu)化改進的算法。和標記清除算法一樣,也是從根節(jié)點開始,對對象的引用進行標記,在清理階段,并不是簡單的清理未標記的對象,而是將存活的對象壓縮到內存的一端,然后清理邊界以外的垃圾,從而解決了碎片化的問題。
1)、原理

2)、優(yōu)缺點
優(yōu)缺點同標記清除算法,解決了標記清除算法的碎片化的問題,同時,標記壓縮算法多了一步,對象移動內存位置的步驟,其效率也有一定的影響。
4、復制算法
復制算法的核心就是,將原有的內存空間一分為二,每次只用其中的一塊,在垃圾回收時,將正在使用的對象復制到另一個內存空間中,然后將該內存空間清空,交換兩個內存的角色,完成垃圾的回收。
如果內存中的垃圾對象較多,需要復制的對象就較少,這種情況下適合使用該方式并且效率比較高,反之,則不適合。
1)、原理

具體分析講解見視頻
2)、JVM中年輕代內存空間
[圖片上傳中...(image-f4e431-1600420999679-25)]
- 在GC開始的時候,對象只會存在于Eden區(qū)和名為“From”的Survivor區(qū),Survivor區(qū)“To”是空的。
- 緊接著進行GC,Eden區(qū)中所有存活的對象都會被復制到“To”,而在“From”區(qū)中,仍存活的對象會根據(jù)他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被復制到“To”區(qū)域。
- 經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個時候,“From”和“To”會交換他們的角色,也就是的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區(qū)域是空的。
- GC會一直重復這樣的過程,直到“To”區(qū)被填滿,“To”區(qū)被填滿之后,會將所有對象移動到年老代中。
3)、優(yōu)缺點
優(yōu)點:
- 在垃圾對象多的情況下,效率較高 。
- 清理后,內存無碎片。
缺點:
- 在垃圾對象少的情況下,不適用,如:老年代內存。
- 分配的2塊內存空間,在同一個時刻,只能使用一半,內存使用率較低
5、分代算法
前面介紹了多種回收算法,每一種算法都有自己的優(yōu)點也有缺點,誰都不能替代誰,所以根據(jù)垃圾回收對象的特點進行選擇,才是明智的選擇。
分代算法其實就是這樣的,根據(jù)回收對象的特點進行選擇,在jvm中,年輕代適合使用復制算法,老年代適合使用標記清除或標記壓縮算法。
三、垃圾收集器及內存分配
前面我們講了垃圾回收的算法,還需要有具體的實現(xiàn),在jvm中,實現(xiàn)了多種垃圾收集器,包括:串行垃圾收集器、并行垃圾收集器、CMS(并發(fā))垃圾收集器、G1垃圾收集器,接下來,我們一個個的了解學習。
垃圾收集器種類

HotSpot虛擬機所包含的收集器

垃圾收集器部分源碼

垃圾收集器后臺日志參數(shù)說明與配對關系
- DefNew - Default New Generation
- Tenured - Old
- ParNew - Parallel New Generation
- PSYoungGen - Parallel Scavenge
- ParOldGen - Parallel Old Generation
以上不同種類,已經(jīng)回收器的配對使用分析講解見視頻
1、串行垃圾收集器
串行垃圾收集器是最基本的、發(fā)展歷史最悠久的收集器。
特點:單線程、簡單高效(與其他收集器的單線程相比),對于限定單個CPU的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。收集器進行垃圾回收時,必須暫停其他所有的工作線程,直到它結束(Stop The World)。
串行垃圾收集器運行示意圖

串行垃圾收集器原理分析見視頻
1)、編寫測試代碼
思路:while循環(huán)中不斷拼接字符串,直到oom異常,查看gc情況
import java.util.UUID;
/**************************************************
*
* @title
* @desc ling
* @author smart哥
*
**************************************************/
public class TestGC1 {
/**
* java -XX:+PrintCommandLineFlags -version
*
* @param args
*/
public static void main(String[] args) {
String str = "smart哥";
while (true) {
str += str + UUID.randomUUID();
str.intern();
}
}
}
2)、設置垃圾回收為串行收集器
在程序運行參數(shù)中添加2個參數(shù),如下:
-XX:+UseSerialGC 指定年輕代和老年代都使用串行垃圾收集器
-XX:+PrintGCDetails 打印垃圾回收的詳細信息
3)、啟動程序,GC日志信息解讀

具體解讀請觀看視頻
2、并行垃圾收集器
并行垃圾收集器在串行垃圾收集器的基礎之上做了改進,將單線程改為了多線程進行垃圾回收,這樣可以縮短垃圾回收的時間。(這里是指,并行能力較強的機器)
當然了,并行垃圾收集器在收集的過程中也會暫停應用程序,這個和串行垃圾回收器是一樣的,只是并行執(zhí)行,速度更快些,暫停的時間更短一些。
并行垃圾收集器-ParNew運行示意圖

ParNew垃圾收集器
通過-XX:+UseParNewGC參數(shù)設置年輕代使用ParNew回收器,老年代使用的依然是串行收集器
通過-XX:+ParallelGCThreads可以限制GC線程數(shù)量,默認開啟和cpu數(shù)目相同的線程數(shù)
具體講解見視頻
1)、編寫測試代碼
同之前的代碼
2)、設置垃圾回收為并行收集器ParNew
在程序運行參數(shù)中添加1個參數(shù),如下
-XX:+UseParNewGC
3)、啟動程序,GC日志信息解讀

具體解讀請觀看視頻
并行垃圾收集器-ParallelGC運行示意圖

ParallelGC垃圾收集器
ParallelGC收集器工作機制和ParNewGC收集器一樣,只是在此基礎之上,新增了兩個和系統(tǒng)吞吐量相關的參數(shù),使得其使用起來更加的靈活和高效。
具體講解見視頻
1)、編寫測試代碼
同之前的代碼
2)、設置垃圾回收為并行收集器ParallelGC
ParallelGC垃圾收集器相關參數(shù)如下:
-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:MaxGCPauseMillis -XX:ParallelGCThreads=N
3)、啟動程序,GC日志信息解讀

具體解讀見視頻
3、CMS垃圾收集器
CMS全稱 Concurrent Mark Sweep,是一款并發(fā)的、使用標記-清除算法的垃圾回收器,該回收器是針對老年代垃圾回收的,通過參數(shù)-XX:+UseConcMarkSweepGC進行設置。
CMS垃圾收集器運行示意圖:

示意圖原理講解見視頻
CMS垃圾回收器的執(zhí)行過程如下:

執(zhí)行過程具體講解見視頻
1)、編寫測試代碼
同之前的代碼
2)、設置CMS垃圾回收參數(shù)
-‐XX:+UseConcMarkSweepGC
注意:開啟后將采用ParNew+CMS+Serial Old收集器組合
3)、啟動程序,GC日志信息解讀

具體解讀見視頻
4、G1垃圾收集器
G1垃圾收集器是在jdk1.7update4中正式使用的全新的垃圾收集器,oracle官方在jdk9中將G1變成默認的垃圾收集器,以替代CMS。
G1的設計原則就是簡化JVM性能調優(yōu),開發(fā)人員只需要簡單的三步即可完成調優(yōu):
- 第一步,開啟G1垃圾收集器
- 第二步,設置堆的最大內存
- 第三步,設置最大的停頓時間
- G1中提供了三種模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的條件下被觸發(fā)。
G1垃圾收集器相對比其他收集器而言,最大的區(qū)別在于它取消了年輕代、老年代的物理劃分,取而代之的是將堆劃分為若干個區(qū)域(Region),這些區(qū)域中包含了有邏輯上的年輕代、老年代區(qū)域。這樣做的好處就是,我們再也不用單獨的空間對每個代進行設置了,不用擔心每個代內存是否足夠。
G1垃圾收集器(將新生代,老年代的物理空間劃分取消了),示意圖如下

G1垃圾收集器(G1算法將堆劃分為若干個區(qū)域-Region)

G1垃圾收集器原理


在G1劃分的區(qū)域中,年輕代的垃圾收集依然采用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間,G1收集器通過將對象從一個區(qū)域復制到另外一個區(qū)域,完成了清理工作。
這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內存碎片問題的存在了。
在G1中,有一種特殊的區(qū)域,叫Humongous區(qū)域。如果一個對象占用的空間超過了分區(qū)容量50%以上,G1收集器就認為這是一個巨型對象。
這些巨型對象,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。
為了解決這個問題,G1劃分了一個Humongous區(qū),它用來專門存放巨型對象。如果一個H區(qū)裝不下一個巨型對象,那么G1會尋找連續(xù)的H分區(qū)來存儲。為了能找到連續(xù)的H區(qū),有時候不得不啟動Full GC。
針對Young GC主要是對Eden區(qū)進行GC,它在Eden空間耗盡時會被觸發(fā)。Eden空間的數(shù)據(jù)移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數(shù)據(jù)會直接晉升到年老代空間。
Survivor區(qū)的數(shù)據(jù)移動到新Survivor區(qū)中,也有部分數(shù)據(jù)晉升到老年代空間中。最終Eden空間的數(shù)據(jù)為空,GC停止工作,應用線程繼續(xù)執(zhí)行。
G1垃圾回收模式:Young GC

具體模式講解見視頻
G1垃圾回收模式:Mixed GC
分2步:
- 全局并發(fā)標記(global concurrent marking)
- 拷貝存活對象(evacuation)
G1垃圾收集器運行示意圖

具體講解見視頻
1)、編寫測試代碼
同之前的代碼
2)、設置G1垃圾回收參數(shù)
‐XX:+PrintGC 輸出GC日志 ‐XX:+PrintGCDetails 輸出GC的詳細日志 ‐XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式) ‐XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013‐05‐04T21:53:59.234+0800) ‐XX:+PrintHeapAtGC 在進行GC的前后打印出堆的信息 ‐Xloggc:F://test//gc.log 日志文件的輸出路徑
3)、啟動程序,GC日志信息解讀

G1垃圾收集器 vs CMS垃圾收集器
- G1不會產生碎片
- G1可以精準控制停頓,它把整堆劃分為多個固定大小的區(qū)域,每次根據(jù)停頓時間去收集垃圾最多的區(qū)域
G1垃圾收集器優(yōu)化建議
-
年輕代大小
- 避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設置年輕代大小
- 固定年輕代的大小會覆蓋暫停時間目標
-
暫停時間目標不要太過嚴苛
- G1 GC 的吞吐量目標是 90% 的應用程序時間和 10%的垃圾回收時間
- 評估 G1 GC 的吞吐量時,暫停時間目標不要太嚴苛。目標太過嚴苛表示您愿意承受更多的垃圾回收開銷,而這會直接影響到吞吐量
五、可視化GC日志分析工具
1、GC日志輸出參數(shù)
前面通過-XX:+PrintGCDetails可以對GC日志進行打印,我們就可以在控制臺查看,這樣雖然可以查看GC的信息,但是并不直觀,可以借助于第三方的GC日志分析工具進行查看。 在日志打印輸出涉及到的參數(shù)如下:
‐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 日志文件的輸出路徑
測試:
‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xmx256m ‐XX:+PrintGCDetails ‐
XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐
Xloggc:F://test//gc.log
最后生成gc.log,我們利用下面的可視化工具進行分析。
[](https://smartan123.github.io/book/?file=001-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/002-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/0024-JVM%E8%B0%83%E4%BC%98-GC%E7%AF%87#2、GC Easy可視化工具)2、GC Easy可視化工具
GC Easy是一款在線的可視化工具,易用、功能強大, GCEasy官網(wǎng)地址:http://gceasy.io/
打開官網(wǎng)上傳gc.log,點擊分析即可。分析完之后它會給我們出相關的分析報告,那查看指標如何解讀呢?
具體解讀見視頻
GC Easy查看gc報告





分析報告解讀請觀看視頻
六、本章小結
