1. 垃圾回收機(jī)制
Stop-the-World:
JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行稱之為Stop-the-World,該情形會(huì)在任何一種GC算法中發(fā)生。當(dāng)Stop-the-world發(fā)生時(shí),除了GC所需的線程以外,所有線程都處于等待狀態(tài)直到GC任務(wù)完成。事實(shí)上,GC優(yōu)化很多時(shí)候就是指減少Stop-the-world發(fā)生的時(shí)間,從而使系統(tǒng)具有 高吞吐 、低停頓 的特點(diǎn)。
2. java運(yùn)行時(shí)的內(nèi)存劃分

1. 程序計(jì)數(shù)器
記錄當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào),用于獲取下一條執(zhí)行的字節(jié)碼。
當(dāng)多線程運(yùn)行時(shí),每個(gè)線程切換后需要知道上一次所運(yùn)行的狀態(tài)、位置。
由此也可以看出程序計(jì)數(shù)器是每個(gè)線程私有的。
3. 虛擬機(jī)棧
虛擬機(jī)棧由一個(gè)一個(gè)的棧幀組成,棧幀是在每一個(gè)方法調(diào)用時(shí)產(chǎn)生的。
每一個(gè)棧幀由局部變量區(qū)、操作數(shù)棧等組成。每創(chuàng)建一個(gè)棧幀壓棧,當(dāng)一個(gè)方法執(zhí)行完畢之后則出棧。
- 如果出現(xiàn)方法遞歸調(diào)用出現(xiàn)死循環(huán)的話就會(huì)造成棧幀過多,最終會(huì)拋出 StackOverflowError。
- 若線程執(zhí)行過程中棧幀大小超出虛擬機(jī)棧限制,則會(huì)拋出 StackOverflowError。
- 若虛擬機(jī)棧允許動(dòng)態(tài)擴(kuò)展,但在嘗試擴(kuò)展時(shí)內(nèi)存不足,或者在為一個(gè)新線程初始化新的虛擬機(jī)棧時(shí)申請(qǐng)不到足夠的內(nèi)存,則會(huì)拋出 OutOfMemoryError。
這塊內(nèi)存區(qū)域也是線程私有的。
4. java堆
Java 堆是整個(gè)虛擬機(jī)所管理的最大內(nèi)存區(qū)域,所有的對(duì)象創(chuàng)建都是在這個(gè)區(qū)域進(jìn)行內(nèi)存分配。
可利用參數(shù) -Xms -Xmx 進(jìn)行堆內(nèi)存控制。
這塊區(qū)域也是垃圾回收器重點(diǎn)管理的區(qū)域,由于大多數(shù)垃圾回收器都采用分代回收算法,所有堆內(nèi)存也分為 新生代、老年代,可以方便垃圾的準(zhǔn)確回收。
這塊內(nèi)存屬于線程共享區(qū)域。
5. 方法區(qū)
方法區(qū)主要用于存放已經(jīng)被虛擬機(jī)加載的類信息,如常量,靜態(tài)變量。 這塊區(qū)域也被稱為永久代。
可利用參數(shù) -XX:PermSize -XX:MaxPermSize 控制初始化方法區(qū)和最大方法區(qū)大小。
6. 元數(shù)據(jù)區(qū)
在 JDK1.8 中已經(jīng)移除了方法區(qū)(永久代),并使用了一個(gè)元數(shù)據(jù)區(qū)域進(jìn)行代替(Metaspace)。
默認(rèn)情況下元數(shù)據(jù)區(qū)域會(huì)根據(jù)使用情況動(dòng)態(tài)調(diào)整,避免了在 1.7 中由于加載類過多從而出現(xiàn) java.lang.OutOfMemoryError: PermGen。
但也不能無線擴(kuò)展,因此可以使用 -XX:MaxMetaspaceSize來控制最大內(nèi)存。
7. 運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是方法區(qū)的一部分,其中存放了一些符號(hào)引用。當(dāng) new 一個(gè)對(duì)象時(shí),會(huì)檢查這個(gè)區(qū)域是否有這個(gè)符號(hào)的引用。
8. 直接內(nèi)存
直接內(nèi)存又稱為 Direct Memory(堆外內(nèi)存),它并不是由 JVM 虛擬機(jī)所管理的一塊內(nèi)存區(qū)域。
有使用過 Netty 的朋友應(yīng)該對(duì)這塊并內(nèi)存不陌生,在 Netty 中所有的 IO(nio) 操作都會(huì)通過 Native 函數(shù)直接分配堆外內(nèi)存。
它是通過在堆內(nèi)存中的 DirectByteBuffer 對(duì)象操作的堆外內(nèi)存,避免了堆內(nèi)存和堆外內(nèi)存來回復(fù)制交換復(fù)制,這樣的高效操作也稱為零拷貝。
既然是內(nèi)存,那也得是可以被回收的。但由于堆外內(nèi)存不直接受 JVM 管理,所以常規(guī) GC 操作并不能回收堆外內(nèi)存。它是借助于老年代產(chǎn)生的 fullGC 順便進(jìn)行回收。同時(shí)也可以顯式調(diào)用 System.gc() 方法進(jìn)行回收(前提是沒有使用 -XX:+DisableExplicitGC 參數(shù)來禁止該方法)。
值得注意的是:由于堆外內(nèi)存也是內(nèi)存,是由操作系統(tǒng)管理。如果應(yīng)用有使用堆外內(nèi)存則需要平衡虛擬機(jī)的堆內(nèi)存和堆外內(nèi)存的使用占比。避免出現(xiàn)堆外內(nèi)存溢出。
9. 常用參數(shù)

通過上圖可以直觀的查看各個(gè)區(qū)域的參數(shù)設(shè)置。
常見的如下:
-Xms64m 最小堆內(nèi)存 64m.
-Xmx128m 最大堆內(nèi)存 128m.
-XX:NewSize=30m 新生代初始化大小為30m.
-XX:MaxNewSize=40m 新生代最大大小為40m.
-Xss=256k 線程棧大小。
-XX:+PrintHeapAtGC 當(dāng)發(fā)生 GC 時(shí)打印內(nèi)存布局。
-XX:+HeapDumpOnOutOfMemoryError 發(fā)送內(nèi)存溢出時(shí) dump 內(nèi)存。
新生代和老年代的默認(rèn)比例為 1:2,也就是說新生代占用 1/3的堆內(nèi)存,而老年代占用 2/3 的堆內(nèi)存。
可以通過參數(shù) -XX:NewRatio=2 來設(shè)置老年代/新生代的比例。
對(duì)象的創(chuàng)建
下圖便是 Java 對(duì)象的創(chuàng)建過程,我建議最好是能默寫出來,并且要掌握每一步在做什么。

Step1:類加載檢查
虛擬機(jī)遇到一條 new 指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。
Step2:分配內(nèi)存
在類加載檢查通過后,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內(nèi)存分配的兩種方式:(補(bǔ)充內(nèi)容,需要掌握)
選擇以上兩種方式中的哪一種,取決于 Java 堆內(nèi)存是否規(guī)整。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除",還是"標(biāo)記-整理"(也稱作"標(biāo)記-壓縮"),值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的

內(nèi)存分配并發(fā)問題(補(bǔ)充內(nèi)容,需要掌握)
在創(chuàng)建對(duì)象的時(shí)候有一個(gè)很重要的問題,就是線程安全,因?yàn)樵趯?shí)際開發(fā)過程中,創(chuàng)建對(duì)象是很頻繁的事情,作為虛擬機(jī)來說,必須要保證線程是安全的,通常來講,虛擬機(jī)采用兩種方式來保證線程安全:
CAS+失敗重試: CAS 是樂觀鎖的一種實(shí)現(xiàn)方式。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性。
TLAB: 為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存,JVM 在給線程中的對(duì)象分配內(nèi)存時(shí),首先在 TLAB 分配,當(dāng)對(duì)象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí),再采用上述的 CAS 進(jìn)行內(nèi)存分配
Step3:初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
Step4:設(shè)置對(duì)象頭
初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是那個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息。 這些信息存放在對(duì)象頭中。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。
Step5:執(zhí)行 init 方法
在上面工作都完成之后,從虛擬機(jī)的視角來看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來看,對(duì)象創(chuàng)建才剛開始,<init> 方法還沒有執(zhí)行,所有的字段都還為零。所以一般來說,執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init>方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來。
4. 垃圾回收
GC分為 Minor GC (年輕代回收)和Full GC(老年代回收)
垃圾回收主要思考三件事情:
- 哪種內(nèi)存需要回收?
- 什么時(shí)候回收?
-
怎么回收?
image.png
引用計(jì)數(shù)法
這是一種非常簡(jiǎn)單易理解的回收算法。每當(dāng)有一個(gè)地方引用一個(gè)對(duì)象的時(shí)候則在引用計(jì)數(shù)器上 +1,當(dāng)失效的時(shí)候就 -1,無論什么時(shí)候計(jì)數(shù)器為 0 的時(shí)候則認(rèn)為該對(duì)象死亡可以回收了。
這種算法雖然簡(jiǎn)單高效,但是卻無法解決循環(huán)引用的問題,因此 Java 虛擬機(jī)并沒有采用這種算法。
可達(dá)性分析算法
主流的語言其實(shí)都是采用可達(dá)性分析算法:
可達(dá)性算法是通過一個(gè)稱為 GC Roots 的對(duì)象向下搜索,整個(gè)搜索路徑就稱為引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈 JVM 就認(rèn)為該對(duì)象是可以被回收的。

如圖:Object1、2、3、4 都是存活的對(duì)象,而 Object5、6、7都是可回收對(duì)象。
可以用作 GC-Roots 的對(duì)象有:
方法區(qū)中靜態(tài)變量所引用的對(duì)象。
虛擬機(jī)棧中所引用的對(duì)象。
再談引用
無論是通過引用計(jì)數(shù)法判斷對(duì)象引用數(shù)量,還是通過可達(dá)性分析法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá),判定對(duì)象的存活都與“引用”有關(guān)。
JDK1.2 之前,Java 中引用的定義很傳統(tǒng):如果 reference 類型的數(shù)據(jù)存儲(chǔ)的數(shù)值代表的是另一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表一個(gè)引用。
JDK1.2 以后,Java 對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用、軟引用、弱引用、虛引用四種(引用強(qiáng)度逐漸減弱)
1.強(qiáng)引用
以前我們使用的大部分引用實(shí)際上都是強(qiáng)引用,這是使用最普遍的引用。如果一個(gè)對(duì)象具有強(qiáng)引用,那就類似于必不可少的生活用品,垃圾回收器絕不會(huì)回收它。當(dāng)內(nèi)存空 間不足,Java 虛擬機(jī)寧愿拋出 OutOfMemoryError 錯(cuò)誤,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來解決內(nèi)存不足問題。
2.軟引用(SoftReference)
如果一個(gè)對(duì)象只具有軟引用,那就類似于可有可無的生活用品。如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它,如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒有回收它,該對(duì)象就可以被程序使用。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收,JAVA 虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
3.弱引用(WeakReference)
如果一個(gè)對(duì)象只具有弱引用,那就類似于可有可無的生活用品。弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它 所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程, 因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。
弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java 虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
4.虛引用(PhantomReference)
"虛引用"顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收。
虛引用主要用來跟蹤對(duì)象被垃圾回收的活動(dòng)。
虛引用與軟引用和弱引用的一個(gè)區(qū)別在于: 虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。當(dāng)垃 圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是 否已經(jīng)加入了虛引用,來了解被引用的對(duì)象是否將要被垃圾回收。程序如果發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)。
特別注意,在程序設(shè)計(jì)中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因?yàn)檐浺每梢约铀?JVM 對(duì)垃圾內(nèi)存的回收速度,可以維護(hù)系統(tǒng)的運(yùn)行安全,防止內(nèi)存溢出(OutOfMemory)等問題的產(chǎn)生。
如何判斷一個(gè)常量是廢棄常量
運(yùn)行時(shí)常量池主要回收的是廢棄的常量。那么,我們?nèi)绾闻袛嘁粋€(gè)常量是廢棄常量呢?
假如在常量池中存在字符串 "abc",如果當(dāng)前沒有任何 String 對(duì)象引用該字符串常量的話,就說明常量 "abc" 就是廢棄常量,如果這時(shí)發(fā)生內(nèi)存回收的話而且有必要的話,"abc" 就會(huì)被系統(tǒng)清理出常量池。如何判斷一個(gè)類是無用的類
方法區(qū)主要回收的是無用的類,那么如何判斷一個(gè)類是無用的類的呢?
判定一個(gè)常量是否是“廢棄常量”比較簡(jiǎn)單,而要判定一個(gè)類是否是“無用的類”的條件則相對(duì)苛刻許多。類需要同時(shí)滿足下面 3 個(gè)條件才能算是 “無用的類” :
- 該類所有的實(shí)例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實(shí)例。
- 加載該類的 ClassLoader 已經(jīng)被回收。
- 該類對(duì)應(yīng)的 java.lang.Class 對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機(jī)可以對(duì)滿足上述 3 個(gè)條件的無用類進(jìn)行回收,這里說的僅僅是“可以”,而并不是和對(duì)象一樣不使用了就會(huì)必然被回收。
垃圾回收算法

1.標(biāo)記清楚法
標(biāo)記清除算法分為兩個(gè)步驟,標(biāo)記和清除。 首先將不需要回收的對(duì)象標(biāo)記起來,然后再清除其余可回收對(duì)象。但是存在兩個(gè)主要的問題:
標(biāo)記和清除的效率都不高。
清除之后容易出現(xiàn)不連續(xù)內(nèi)存,當(dāng)需要分配一個(gè)較大內(nèi)存時(shí)就不得不需要進(jìn)行一次垃圾回收。
標(biāo)記清除過程如下:

2. 復(fù)制算法
復(fù)制算法是將內(nèi)存劃分為兩塊大小相等的區(qū)域,每次使用時(shí)都只用其中一塊區(qū)域,當(dāng)發(fā)生垃圾回收時(shí)會(huì)將存活的對(duì)象全部復(fù)制到未使用的區(qū)域,然后對(duì)之前的區(qū)域進(jìn)行全部回收。
這樣簡(jiǎn)單高效,而且還不存在標(biāo)記清除算法中的內(nèi)存碎片問題,但就是有點(diǎn)浪費(fèi)內(nèi)存。
在新生代會(huì)使用該算法。
新生代中分為一個(gè) Eden 區(qū)和兩個(gè) Survivor 區(qū)。通常兩個(gè)區(qū)域的比例是 8:1:1 ,使用時(shí)會(huì)用到 Eden 區(qū)和其中一個(gè) Survivor 區(qū)。當(dāng)發(fā)生回收時(shí)則會(huì)將還存活的對(duì)象從 Eden ,Survivor 區(qū)拷貝到另一個(gè) Survivor 區(qū),當(dāng)該區(qū)域內(nèi)存也不足時(shí)則會(huì)使用分配擔(dān)保利用老年代來存放內(nèi)存。
復(fù)制算法過程:

3. 標(biāo)記整理算法
復(fù)制算法如果在存活對(duì)象較多時(shí)效率明顯會(huì)降低,特別是在老年代中并沒有多余的內(nèi)存區(qū)域可以提供內(nèi)存擔(dān)保。
所以老年代中使用的時(shí)候標(biāo)記整理算法,它的原理和標(biāo)記清除算法類似,只是最后一步的清除改為了將存活對(duì)象全部移動(dòng)到一端,然后再將邊界之外的內(nèi)存全部回收。

4. 分代回收算法
現(xiàn)代多數(shù)的商用 JVM 的垃圾收集器都是采用的分代回收算法,和之前所提到的算法并沒有新的內(nèi)容。
只是將 Java 堆分為了新生代和老年代。由于新生代中存活對(duì)象較少,所以采用復(fù)制算法,簡(jiǎn)單高效。
而老年代中對(duì)象較多,并且沒有可以擔(dān)保的內(nèi)存區(qū)域,所以一般采用標(biāo)記清除或者是標(biāo)記整理算法。
垃圾回收器:

如果說收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。
雖然我們對(duì)各個(gè)收集器進(jìn)行比較,但并非要挑選出一個(gè)最好的收集器。因?yàn)橹钡浆F(xiàn)在為止還沒有最好的垃圾收集器出現(xiàn),更加沒有萬能的垃圾收集器,我們能做的就是根據(jù)具體應(yīng)用場(chǎng)景選擇適合自己的垃圾收集器。試想一下:如果有一種四海之內(nèi)、任何場(chǎng)景下都適用的完美收集器存在,那么我們的 HotSpot 虛擬機(jī)就不會(huì)實(shí)現(xiàn)那么多不同的垃圾收集器了。
4.1 Serial 收集器
Serial(串行)收集器收集器是最基本、歷史最悠久的垃圾收集器了。大家看名字就知道這個(gè)收集器是一個(gè)單線程收集器了。它的 “單線程” 的意義不僅僅意味著它只會(huì)使用一條垃圾收集線程去完成垃圾收集工作,更重要的是它在進(jìn)行垃圾收集工作的時(shí)候必須暫停其他所有的工作線程( "Stop The World" ),直到它收集結(jié)束。
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。 [圖片上傳失敗...(image-6f5750-1573721326194)]

虛擬機(jī)的設(shè)計(jì)者們當(dāng)然知道 Stop The World 帶來的不良用戶體驗(yàn),所以在后續(xù)的垃圾收集器設(shè)計(jì)中停頓時(shí)間在不斷縮短(仍然還有停頓,尋找最優(yōu)秀的垃圾收集器的過程仍然在繼續(xù))。
但是 Serial 收集器有沒有優(yōu)于其他垃圾收集器的地方呢?當(dāng)然有,它簡(jiǎn)單而高效(與其他收集器的單線程相比)。Serial 收集器由于沒有線程交互的開銷,自然可以獲得很高的單線程收集效率。Serial 收集器對(duì)于運(yùn)行在 Client 模式下的虛擬機(jī)來說是個(gè)不錯(cuò)的選擇。
4.2 ParNew 收集器
ParNew 收集器其實(shí)就是 Serial 收集器的多線程版本,除了使用多線程進(jìn)行垃圾收集外,其余行為(控制參數(shù)、收集算法、回收策略等等)和 Serial 收集器完全一樣。
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。 [圖片上傳失敗...(image-8105b4-1573721326194)]

它是許多運(yùn)行在 Server 模式下的虛擬機(jī)的首要選擇,除了 Serial 收集器外,只有它能與 CMS 收集器(真正意義上的并發(fā)收集器,后面會(huì)介紹到)配合工作。
并行和并發(fā)概念補(bǔ)充:
并行(Parallel) :指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)。
并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行,可能會(huì)交替執(zhí)行),用戶程序在繼續(xù)運(yùn)行,而垃圾收集器運(yùn)行在另一個(gè) CPU 上。
4.3 Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用復(fù)制算法的多線程收集器,它看上去幾乎和ParNew都一樣。 那么它有什么特別之處呢?
-XX:+UseParallelGC
使用 Parallel 收集器+ 老年代串行
-XX:+UseParallelOldGC
使用 Parallel 收集器+ 老年代并行
Parallel Scavenge 收集器關(guān)注點(diǎn)是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的關(guān)注點(diǎn)更多的是用戶線程的停頓時(shí)間(提高用戶體驗(yàn))。所謂吞吐量就是 CPU 中用于運(yùn)行用戶代碼的時(shí)間與 CPU 總消耗時(shí)間的比值。 Parallel Scavenge 收集器提供了很多參數(shù)供用戶找到最合適的停頓時(shí)間或最大吞吐量,如果對(duì)于收集器運(yùn)作不太了解的話,手工優(yōu)化存在的話可以選擇把內(nèi)存管理優(yōu)化交給虛擬機(jī)去完成也是一個(gè)不錯(cuò)的選擇。
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法。 [圖片上傳失敗...(image-74d33a-1573721326194)]

4.4.Serial Old 收集器
Serial 收集器的老年代版本,它同樣是一個(gè)單線程收集器。它主要有兩大用途:一種用途是在 JDK1.5 以及以前的版本中與 Parallel Scavenge 收集器搭配使用,另一種用途是作為 CMS 收集器的后備方案。
4.5 Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。使用多線程和“標(biāo)記-整理”算法。在注重吞吐量以及 CPU 資源的場(chǎng)合,都可以優(yōu)先考慮 Parallel Scavenge 收集器和 Parallel Old 收集器。
4.6 CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。它而非常符合在注重用戶體驗(yàn)的應(yīng)用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機(jī)第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。
從名字中的Mark Sweep這兩個(gè)詞可以看出,CMS 收集器是一種 “標(biāo)記-清除”算法實(shí)現(xiàn)的,它的運(yùn)作過程相比于前面幾種垃圾收集器來說更加復(fù)雜一些。整個(gè)過程分為四個(gè)步驟:
初始標(biāo)記: 暫停所有的其他線程,并記錄下直接與 root 相連的對(duì)象,速度很快 ;
并發(fā)標(biāo)記: 同時(shí)開啟 GC 和用戶線程,用一個(gè)閉包結(jié)構(gòu)去記錄可達(dá)對(duì)象。但在這個(gè)階段結(jié)束,這個(gè)閉包結(jié)構(gòu)并不能保證包含當(dāng)前所有的可達(dá)對(duì)象。因?yàn)橛脩艟€程可能會(huì)不斷的更新引用域,所以 GC 線程無法保證可達(dá)性分析的實(shí)時(shí)性。所以這個(gè)算法里會(huì)跟蹤記錄這些發(fā)生引用更新的地方。
重新標(biāo)記: 重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長(zhǎng),遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時(shí)間短
并發(fā)清除: 開啟用戶線程,同時(shí) GC 線程開始對(duì)為標(biāo)記的區(qū)域做清掃。
[圖片上傳失敗...(image-259479-1573721326194)]

從它的名字就可以看出它是一款優(yōu)秀的垃圾收集器,主要優(yōu)點(diǎn):并發(fā)收集、低停頓。但是它有下面三個(gè)明顯的缺點(diǎn):
對(duì) CPU 資源敏感;
無法處理浮動(dòng)垃圾;
它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。
4.7 G1 收集器
G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征.
被視為 JDK1.7 中 HotSpot 虛擬機(jī)的一個(gè)重要進(jìn)化特征。它具備一下特點(diǎn):
并行與并發(fā):G1 能充分利用 CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行。
分代收集:雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆,但是還是保留了分代的概念。
空間整合:與 CMS 的“標(biāo)記--清理”算法不同,G1 從整體來看是基于“標(biāo)記整理”算法實(shí)現(xiàn)的收集器;從局部上來看是基于“復(fù)制”算法實(shí)現(xiàn)的。
可預(yù)測(cè)的停頓:這是 G1 相對(duì)于 CMS 的另一個(gè)大優(yōu)勢(shì),降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn),但 G1 除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為 M 毫秒的時(shí)間片段內(nèi)。
G1 收集器的運(yùn)作大致分為以下幾個(gè)步驟:
初始標(biāo)記
并發(fā)標(biāo)記
最終標(biāo)記
篩選回收
G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來)。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了 GF 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)。
JVM 調(diào)優(yōu)
JVM 調(diào)優(yōu)的主要目標(biāo)是使系統(tǒng)具有 高吞吐 、低停頓 的特點(diǎn),其優(yōu)化手段應(yīng)從兩方面著手:Java虛擬機(jī) 和 Java應(yīng)用程序。前者指根據(jù)應(yīng)用程序的設(shè)計(jì)通過虛擬機(jī)參數(shù)控制虛擬機(jī)邏輯內(nèi)存分區(qū)的大小以使虛擬機(jī)的內(nèi)存與程序?qū)?nèi)存的需求相得益彰;后者指優(yōu)化程序算法,降低GC負(fù)擔(dān),提高GC回收成功率
類加載機(jī)制
類加載器工作機(jī)制:
1.裝載:將Java二進(jìn)制代碼導(dǎo)入jvm中,生成Class文件。
2.連接:a)校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性 b)準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間 c)解析:將符號(hào)引用轉(zhuǎn)成直接引用
3:初始化:對(duì)類的靜態(tài)變量,靜態(tài)方法和靜態(tài)代碼塊執(zhí)行初始化工作。

1、 加載(Loading)
(1). 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流(并沒有指明要從一個(gè)Class文件中獲取,可以從其他渠道,譬如:網(wǎng)絡(luò)、動(dòng)態(tài)生成、數(shù)據(jù)庫等);
(2). 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
(3). 在內(nèi)存中(對(duì)于HotSpot虛擬就而言就是方法區(qū))生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口;
2、 驗(yàn)證(Verification):驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
3、準(zhǔn)備(Preparation):準(zhǔn)備階段是正式為類變量(static 成員變量)分配內(nèi)存并設(shè)置類變量初始值(零值)的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
4、解析(Resolution):解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。
5、初始化(Initialization):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。虛擬機(jī)會(huì)保證一個(gè)類的類構(gòu)造器<clinit>()在多線程環(huán)境中被正確的加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的類構(gòu)造器<clinit>(),其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。特別需要注意的是,在這種情形下,其他線程雖然會(huì)被阻塞,但如果執(zhí)行<clinit>()方法的那條線程退出后,其他線程在喚醒之后不會(huì)再次進(jìn)入/執(zhí)行<clinit>()方法,因?yàn)?在同一個(gè)類加載器下,一個(gè)類型只會(huì)被初始化一次。
雙親委派模型:類加載器收到類加載請(qǐng)求,首先將請(qǐng)求委派給父類加載器完成 用戶自定義加載器->應(yīng)用程序加載器->擴(kuò)展類加載器->啟動(dòng)類加載器。
雙親委派模型如下圖:

雙親委派模型中除了啟動(dòng)類加載器之外其余都需要有自己的父類加載器
當(dāng)一個(gè)類收到了類加載請(qǐng)求時(shí): 自己不會(huì)首先加載,而是委派給父加載器進(jìn)行加載,每個(gè)層次的加載器都是這樣。
所以最終每個(gè)加載請(qǐng)求都會(huì)經(jīng)過啟動(dòng)類加載器。只有當(dāng)父類加載返回不能加載時(shí)子加載器才會(huì)進(jìn)行加載。
