欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報(bào)邀請(qǐng)碼778899分享:java JVM概念

柚子快報(bào)邀請(qǐng)碼778899分享:java JVM概念

http://yzkb.51969.com/

目錄

1、說說你了解的JVM內(nèi)存模型

2、簡單說下你對(duì)JVM的了解

3、說說類加載機(jī)制

4、說說對(duì)象的實(shí)例化過程

5、說說JVM的雙親委派模型

6、說說JVM調(diào)優(yōu)思路

7、項(xiàng)目中有沒有實(shí)際的JVM調(diào)優(yōu)經(jīng)驗(yàn)?

7.1 CPU飆升?

7.2 GC調(diào)優(yōu)

8、請(qǐng)你說說內(nèi)存溢出

9、請(qǐng)你說說內(nèi)存泄漏

10、JVM中一次完整的GC流程是怎樣的

11、說說JVM的垃圾回收機(jī)制

12、說說GC的可達(dá)性分析算法

13、說說JVM的垃圾回收算法

14、說說七個(gè)垃圾回收器

15、請(qǐng)你講下CMS(并發(fā)標(biāo)記清除)回收器

16、請(qǐng)你講下G1垃圾優(yōu)先回收器

1、說說你了解的JVM內(nèi)存模型

得分點(diǎn)?

類加載子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎

JVM由三部分組成:類加載子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎

類加載子系統(tǒng):通過類加載機(jī)制加載類的class文件,如果該類是第一次加載,會(huì)執(zhí)行加載、驗(yàn)證、解析。只負(fù)責(zé)class文件的加載,至于是否可運(yùn)行,則由執(zhí)行引擎決定。

類加載過程是在類加載子系統(tǒng)完成的:加載 --> 鏈接(驗(yàn)證 --> 準(zhǔn)備 --> 解析) --> 初始化

運(yùn)行時(shí)數(shù)據(jù)區(qū):

在程序運(yùn)行時(shí),存儲(chǔ)程序的內(nèi)容(例如字節(jié)碼、對(duì)象、參數(shù)、返回值等)。運(yùn)行時(shí)數(shù)據(jù)區(qū)包括本地方法棧、虛擬機(jī)棧、方法區(qū)、堆、程序計(jì)數(shù)器。

只有方法區(qū)和堆是各線程共享的進(jìn)程內(nèi)存區(qū)域,其他運(yùn)行區(qū)都是每個(gè)線程可以獨(dú)立擁有的。

本地方法棧:存放本地方法調(diào)用過程中的棧幀。用于管理本地方法的調(diào)用,本地方法是C語言寫的。不是所有虛擬機(jī)都支持本地方法棧,例如Hotspot虛擬機(jī)就是將本地方法棧和虛擬機(jī)棧合二為一。棧解決程序的運(yùn)行問題,即程序如何執(zhí)行、如何處理數(shù)據(jù)。

棧幀:棧幀是棧的元素,由三部分組成,即局部變量表(存方法參數(shù)和局部變量)、操作數(shù)棧(存方法執(zhí)行過程中的中間結(jié)果,或者其他暫存數(shù)據(jù))和幀數(shù)據(jù)區(qū)(存方法返回地址、線程引用等附加信息)。虛擬機(jī)棧:存放Java方法調(diào)用過程中的棧幀。用于管理Java方法的調(diào)用,Java方法是開發(fā)時(shí)寫的Java方法。方法區(qū):可以看作是一塊獨(dú)立于Java堆的內(nèi)存空間,方法區(qū)是各線程共享的內(nèi)存區(qū)域。

方法區(qū)和永久代、元空間的關(guān)系:方法區(qū)是一個(gè)抽象概念,永久代和元空間是方法區(qū)的實(shí)現(xiàn)方式。

永久代:屬于JVM方法區(qū)的內(nèi)存,用來存儲(chǔ)類的元數(shù)據(jù),如類名、方法信息、字段信息等一些靜態(tài)的數(shù)據(jù)。JDK7及之前方法區(qū)也叫永久代。缺點(diǎn)是內(nèi)存大小固定,容易出現(xiàn)oom問題??梢酝ㄟ^-XX:PermSize設(shè)置永久代大小。永久代對(duì)象只能通過Major GC(又稱Full GC)進(jìn)行垃圾回收。元空間:是Hotspot在JDK8引入的,用于取代永久代。元空間屬于本地內(nèi)存,由操作系統(tǒng)直接管理,不再受JVM管理。同時(shí)內(nèi)存空間可以自動(dòng)擴(kuò)容,避免內(nèi)存溢出。默認(rèn)情況下元空間可以無限使用本地內(nèi)存,也可以通過-XX:MetaspaceSize限制內(nèi)存大小。常量池:就是一張表,JVM根據(jù)這張常量表找到要執(zhí)行的類信息和方法信息

類常量池:是.class字節(jié)碼文件中的資源倉庫,主要存放字面量(表示字符串值和數(shù)值,例如字符串值"abc"、final常量、靜態(tài)變量)和符號(hào)引用(類和接口的全限定名、字段名、方法名)。運(yùn)行時(shí)常量池:類加載的“加載”階段會(huì)創(chuàng)建運(yùn)行時(shí)常量池,統(tǒng)一存放各個(gè)類常量池去重后的符號(hào)引用。在類加載的“解析”階段JVM會(huì)把運(yùn)行時(shí)常量池的這些符號(hào)引用轉(zhuǎn)為直接引用。類常量池。類常量池在字節(jié)碼文件中的,運(yùn)行時(shí)常量池在內(nèi)存中。字符串常量池:專門針對(duì)String類型設(shè)計(jì)的常量池。是當(dāng)前應(yīng)用程序里所有線程共享的,每個(gè)jvm只有一個(gè)字符串常量池。存儲(chǔ)字符串對(duì)象的引用。在創(chuàng)建String對(duì)象時(shí),JVM會(huì)先在字符串常量池尋找是否已存在相同字符串的引用,如果有的話就直接返回引用,沒的話就在堆中創(chuàng)建一個(gè)對(duì)象,然后常量池保存這個(gè)引用并返回引用。堆:存放對(duì)象實(shí)例、實(shí)例變量、數(shù)組,包括新生代(伊甸園區(qū)、幸存區(qū)S0和S1)和老年代。堆是垃圾收集器管理的內(nèi)存區(qū)域。堆解決的是數(shù)據(jù)存儲(chǔ)的問題,即數(shù)據(jù)怎么放、放在哪兒。堆實(shí)際內(nèi)存空間可以不連續(xù),大小可以選擇固定大小或可擴(kuò)展,堆是各線程共享的內(nèi)存區(qū)域。程序計(jì)數(shù)器(PC寄存器):存放下一條字節(jié)碼指令的地址,由執(zhí)行引擎讀取下一條字節(jié)碼指令并轉(zhuǎn)為本地機(jī)器指令進(jìn)行執(zhí)行。是程序控制流(分支、循環(huán)、跳轉(zhuǎn)、線程恢復(fù))的指示器,只有它不會(huì)拋出OutOfMemoryError。每個(gè)線程有自己獨(dú)立的程序計(jì)數(shù)器,以便于線程在切換回來時(shí)能知道下一條指令是什么。程序計(jì)數(shù)器生命周期與線程一致。

執(zhí)行引擎:將字節(jié)碼指令解釋/編譯為對(duì)應(yīng)平臺(tái)上的本地機(jī)器指令。充當(dāng)了將高級(jí)語言翻譯為機(jī)器語言的譯者。執(zhí)行引擎在執(zhí)行過程中需要執(zhí)行什么樣的字節(jié)碼指令依賴于PC寄存器。每當(dāng)執(zhí)行完一項(xiàng)指令操作后,PC寄存器就會(huì)更新下一條需要被執(zhí)行的指令地址。

字節(jié)碼指令(JVM指令):字節(jié)碼文件中的指令,內(nèi)部只包含一些能夠被JVM所識(shí)別的字節(jié)碼指令、符號(hào)表,以及其他輔助信息,不能夠直接運(yùn)行在操作系統(tǒng)之上。本地機(jī)器指令:可以直接運(yùn)行在操作系統(tǒng)之上。

內(nèi)存模型:

內(nèi)存模型里的運(yùn)行時(shí)數(shù)據(jù)區(qū):

JVM由三部分組成:類加載子系統(tǒng)、執(zhí)行引擎、運(yùn)行時(shí)數(shù)據(jù)區(qū)。

1. 類加載子系統(tǒng),可以根據(jù)指定的全限定名來載入類或接口。

2. 運(yùn)行時(shí)數(shù)據(jù)區(qū)。當(dāng)程序運(yùn)行時(shí),JVM需要內(nèi)存來存儲(chǔ)許多內(nèi)容,例如:字節(jié)碼、對(duì)象、參數(shù)、返回值、局部變量、運(yùn)算的中間結(jié)果,等等,JVM會(huì)把這些東西都存儲(chǔ)到運(yùn)行時(shí)數(shù)據(jù)區(qū)中,以便于管理。而運(yùn)行時(shí)數(shù)據(jù)區(qū)又可以分為方法區(qū)、堆、虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器。

3. 執(zhí)行引擎,負(fù)責(zé)執(zhí)行那些包含在被載入類的方法中的指令。

加分回答-運(yùn)行時(shí)數(shù)據(jù)區(qū)

運(yùn)行時(shí)數(shù)據(jù)區(qū)是開發(fā)者重點(diǎn)要關(guān)注的部分,因?yàn)槌绦虻倪\(yùn)行與它密不可分,很多錯(cuò)誤的排查也需要基于對(duì)運(yùn)行時(shí)數(shù)據(jù)區(qū)的理解。在運(yùn)行時(shí)數(shù)據(jù)區(qū)所包含的幾塊內(nèi)存空間中,方法區(qū)和堆是線程之間共享的內(nèi)存區(qū)域,而虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器則是線程私有的區(qū)域,就是說每個(gè)線程都有自己的這個(gè)區(qū)域。?

2、簡單說下你對(duì)JVM的了解

得分點(diǎn)

Java跨平臺(tái)、HotSpot、熱點(diǎn)代碼探測技術(shù)、內(nèi)存模型、垃圾回收算法、垃圾回收器

跨平臺(tái)?

Java跨平臺(tái),JVM不跨平臺(tái)。?

JVM是Java語言跨平臺(tái)的關(guān)鍵,Java在虛擬機(jī)層面隱藏了底層技術(shù)的復(fù)雜性以及機(jī)器與操作系統(tǒng)的差異性。運(yùn)行程序的物理機(jī)千差萬別,而JVM則在千差萬別的物理機(jī)上面建立了統(tǒng)一的運(yùn)行平臺(tái),實(shí)現(xiàn)了在任意一臺(tái)JVM上編譯的程序,都能在任何其他JVM上正常運(yùn)行。這一極大的優(yōu)勢使得Java應(yīng)用的開發(fā)比傳統(tǒng)C/C++應(yīng)用的開發(fā)更高效快捷,程序員可以把主要精力放在具體業(yè)務(wù)邏輯,而不是放在保障物理硬件的兼容性上。通常情況下,一個(gè)程序員只要了解了必要的Java類庫、Java語法,學(xué)習(xí)適當(dāng)?shù)牡谌介_發(fā)框架,就已經(jīng)基本滿足日常開發(fā)的需要了,JVM會(huì)在用戶不知不覺中完成對(duì)硬件平臺(tái)的兼容及對(duì)內(nèi)存等資源的管理工作。

默認(rèn)Java虛擬機(jī)HotSpot?

HotSpot是Sun/OracleJDK和OpenJDK中的默認(rèn)Java虛擬機(jī),也是目前使用范圍最廣的Java虛擬機(jī)。HotSpot既繼承了Sun之前兩款商用虛擬機(jī)的優(yōu)點(diǎn),也有許多自己新的技術(shù)優(yōu)勢,如它名稱中的HotSpot指的就是它的熱點(diǎn)代碼探測技術(shù)。HotSpot的熱點(diǎn)代碼探測能力可以通過執(zhí)行計(jì)數(shù)器找出最具有編譯價(jià)值的代碼,然后通知即時(shí)編譯器以方法為單位進(jìn)行編譯。如果一個(gè)方法被頻繁調(diào)用,或方法中有效循環(huán)次數(shù)很多,將會(huì)分別觸發(fā)標(biāo)準(zhǔn)即時(shí)編譯和棧上替換編譯行為。通過編譯器與解釋器恰當(dāng)?shù)貐f(xié)同工作,可以在最優(yōu)化的程序響應(yīng)時(shí)間與最佳執(zhí)行性能中取得平衡,而且無須等待本地代碼輸出才能執(zhí)行程序,即時(shí)編譯的時(shí)間壓力也相對(duì)減小,這樣有助于引入更復(fù)雜的代碼優(yōu)化技術(shù),輸出質(zhì)量更高的本地代碼。本地方法棧和Java方法棧是合并的。

內(nèi)存模型?

JVM由三部分組成:類加載子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎。

類加載子系統(tǒng):根據(jù)指定的全限定名來載入類或接口。

運(yùn)行時(shí)數(shù)據(jù)區(qū):在程序運(yùn)行時(shí),存儲(chǔ)程序的內(nèi)容,例如:字節(jié)碼、對(duì)象、參數(shù)、返回值等。而運(yùn)行時(shí)數(shù)據(jù)區(qū)又可以分為方法區(qū)、堆、虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器。

執(zhí)行引擎:負(fù)責(zé)執(zhí)行那些包含在被載入類的方法中的指令。

3、說說類加載機(jī)制

得分點(diǎn) 加載、驗(yàn)證、準(zhǔn)備、解析、初始化

標(biāo)準(zhǔn)回答?

類加載過程:加載、鏈接(驗(yàn)證、準(zhǔn)備、解析)、初始化。這個(gè)過程是在類加載子系統(tǒng)完成的。

加載:生成類的Class對(duì)象。

通過類的全限定名獲取該類的二進(jìn)制字節(jié)流(即編譯時(shí)生成的類的class字節(jié)碼文件)將這個(gè)字節(jié)流的靜態(tài)存儲(chǔ)結(jié)構(gòu),轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。包括創(chuàng)建運(yùn)行時(shí)常量池,將類常量池的部分符號(hào)引用放入運(yùn)行時(shí)常量池。在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類各種數(shù)據(jù)的訪問入口。注意類的class對(duì)象是運(yùn)行時(shí)生成的,類的class字節(jié)碼文件是編譯時(shí)生成的。

鏈接:將類的二進(jìn)制數(shù)據(jù)合并到JRE中。該過程分為以下3個(gè)階段:

驗(yàn)證:確保代碼符合JAVA虛擬機(jī)規(guī)范和安全約束。包括文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證。

文件格式驗(yàn)證:驗(yàn)證字節(jié)碼文件是否符合規(guī)范。

魔數(shù):是否魔數(shù)0xCAFEBABE開頭版本號(hào):版本號(hào)是否在JVM兼容范圍常量類型:類常量池里常量類型是否合法索引值:索引值是否指向不存在或不符合類型的常量。元數(shù)據(jù)驗(yàn)證:元數(shù)據(jù)是字節(jié)碼里類的全名、方法信息、字段信息、繼承關(guān)系等。

標(biāo)識(shí)符:驗(yàn)證類名接口名標(biāo)識(shí)符有沒有符合規(guī)范接口實(shí)現(xiàn)方法:有沒有實(shí)現(xiàn)接口的所有方法抽象類實(shí)現(xiàn)方法:有沒有實(shí)現(xiàn)抽象類的所有抽象方法final類:是不是繼承了final類。指令驗(yàn)證:主要校驗(yàn)類的方法體,通過數(shù)據(jù)流和控制流分析,保證方法在運(yùn)行時(shí)不會(huì)危害虛擬機(jī)安全。

類型轉(zhuǎn)換:保證方法體中的類型轉(zhuǎn)換是否有效。例如把某個(gè)類強(qiáng)轉(zhuǎn)成沒繼承關(guān)系的類跳轉(zhuǎn)指令:保證跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上;保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作。符號(hào)引用驗(yàn)證:確保后面解析階段能正常執(zhí)行。

類全限定名地址:驗(yàn)證類全限定名是否能找到對(duì)應(yīng)的類字節(jié)碼文件引用地址:引用指向地址是否存在實(shí)例引用權(quán)限:是否有權(quán)引用準(zhǔn)備:為類變量(即static變量)分配內(nèi)存并賦零值。解析:將方法區(qū)-運(yùn)行時(shí)常量池內(nèi)的符號(hào)引用(類的名字、成員名、標(biāo)識(shí)符)轉(zhuǎn)為直接引用(實(shí)際內(nèi)存地址,不包含任何抽象信息,因此可以直接使用)。

初始化:類變量賦初值、執(zhí)行靜態(tài)語句塊。

?

一個(gè)類型從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期將會(huì)經(jīng)歷加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱為連接,而前五個(gè)階段則是類加載的完整過程。

?

1. 在加載階段JVM需要在內(nèi)存中生成一個(gè)代表這個(gè)類的Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。 2. 驗(yàn)證階段大致上會(huì)完成下面四個(gè)階段的檢驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證。 3. 準(zhǔn)備階段是正式為類中定義變量(靜態(tài)變量)分配到內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都應(yīng)當(dāng)在方法區(qū)中進(jìn)行分配,但必須注意到方法區(qū)本身是一個(gè)邏輯上的區(qū)域。 4. 解析階段是Java虛擬機(jī)將常量池內(nèi)的符號(hào)替換為直接引用的過程,符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo),直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或者一個(gè)能間接定位到目標(biāo)的句柄。 5. 類的初始化階段是類加載過程的最后一個(gè)步驟,直到初始化階段,Java虛擬機(jī)才真正開始執(zhí)行類中編寫的Java程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序。本質(zhì)上,初始化階段就是執(zhí)行類構(gòu)造器的過程。并不是程序員在Java代碼中直接編寫的方法,它是Javac編譯器的自動(dòng)生成物。加分回答 關(guān)于在什么情況下需要開始類加載過程的第一個(gè)階段“加載”,《Java虛擬機(jī)規(guī)范》中并沒有進(jìn)行強(qiáng)制約束,這點(diǎn)可以交給虛擬機(jī)的具體實(shí)現(xiàn)來自由把握。但是對(duì)于初始化階段,《Java虛擬機(jī)規(guī)范》則是嚴(yán)格規(guī)定了有且只有六種情況必須立即對(duì)類進(jìn)行“初始化”: 1. 使用new實(shí)例化對(duì)象、讀寫類的靜態(tài)字段、調(diào)用類的靜態(tài)方法時(shí)。 2. 使用java.lang.reflect包的方法對(duì)類型進(jìn)行反射調(diào)用時(shí)。 3. 當(dāng)初始化類時(shí),若發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則先初始化這個(gè)父類。 4. 虛擬機(jī)啟動(dòng)時(shí),需要指定一個(gè)要執(zhí)行的主類,虛擬機(jī)會(huì)先初始化這個(gè)主類。 5. 當(dāng)使用JDK 7新加入的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。 6. 當(dāng)一個(gè)接口中定義了JDK 8新加入的默認(rèn)方法(被default關(guān)鍵字修飾的接口方法)時(shí),如果有這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化,那該接口要在其之前被初始化。

4、說說對(duì)象的實(shí)例化過程

得分點(diǎn)

類加載、分配內(nèi)存(內(nèi)存規(guī)整和不規(guī)整)、處理并發(fā)安全問題、設(shè)置對(duì)象頭、成員變量賦初值、執(zhí)行構(gòu)造方法

對(duì)象的實(shí)例化過程:

判斷對(duì)應(yīng)類是否加載過:首先JVM檢查在方法區(qū)Metaspace(元空間)的常量池里能否定位到該類的符號(hào)引用,能的話通過符號(hào)引用檢查該類是否加載鏈接初始化過;若沒有則在雙親委派機(jī)制下,當(dāng)前類加載器調(diào)用findClass()方法查找類的.class字節(jié)碼文件,然后調(diào)用loadClass("類全限定名")方法遵循雙親委派機(jī)制加載鏈接初始化類到內(nèi)存中,并生成類的class對(duì)象,作為方法區(qū)這個(gè)類各種數(shù)據(jù)的訪問入口。創(chuàng)建對(duì)象:

分配堆內(nèi)存空間:如果內(nèi)存規(guī)整:(例如標(biāo)記整理算法),采用指針碰撞法為新對(duì)象分配內(nèi)存。如果內(nèi)存不規(guī)整:(有內(nèi)存碎片,例如標(biāo)記清除算法),在空閑列表里找到合適大小的空閑內(nèi)存分配給新對(duì)象?,F(xiàn)在主流虛擬機(jī)新生代都是使用標(biāo)記復(fù)制算法,內(nèi)存都是規(guī)整的。處理并發(fā)安全問題:CAS失敗重試,區(qū)域加鎖,每個(gè)線程分配一塊TLAB內(nèi)存緩沖區(qū)。設(shè)置對(duì)象頭:將哈希碼、GC分代年齡、鎖信息、GC標(biāo)記等存在對(duì)象頭的Mark Word中;成員變量賦初值:若指定了初值則賦指定的值。若未指定初值,則基本類型賦0或false、引用類型賦null。執(zhí)行構(gòu)造方法:有父類的話,子類構(gòu)造方法第一行會(huì)隱式或手動(dòng)顯式地加super()。

指針碰撞法:?指針一直在空閑和已用內(nèi)存中間,分配空間時(shí),指針往空閑內(nèi)存方向移動(dòng)一段距離,使這段距離剛好滿足新對(duì)象內(nèi)存大小。

元空間:是Hotspot在JDK8引入的,用于取代永久代。元空間屬于本地內(nèi)存,由操作系統(tǒng)直接管理,不再受JVM管理。同時(shí)也可以自動(dòng)擴(kuò)容內(nèi)存空間,避免內(nèi)存溢出。默認(rèn)情況下元空間可以無限使用本地內(nèi)存,也可以通過-XX:MetaspaceSize限制內(nèi)存大小。

指針碰撞法:?所有用過的內(nèi)存在一邊,空閑的內(nèi)存在另外一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就僅僅是把指針向空閑那邊挪動(dòng)一段與對(duì)象大小相等的距離罷了。

如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機(jī)采用這種分配方式。 一般使用帶有compact( 整理)過程的收集器時(shí),使用指針碰撞。

回顧synchronized用到的對(duì)象頭:

前面多線程篇有提到,synchronized鎖基于對(duì)象頭的Mark Word,鎖升級(jí)四個(gè)狀態(tài)里,偏向鎖和輕量級(jí)鎖基于CAS原子替換,重量級(jí)鎖基于Monitor對(duì)象。對(duì)象頭里Mark Word存哈希碼、GC標(biāo)記、鎖信息。對(duì)象頭里類型指針指向當(dāng)前對(duì)象所在的類。

鎖信息:

鎖標(biāo)志位:01未鎖定、01可偏向、00輕量級(jí)鎖、10重量級(jí)鎖、11垃圾回收標(biāo)記 偏向鎖線程ID、時(shí)間戳等 輕量級(jí)鎖的指針:指向鎖記錄的指針 重量級(jí)鎖的指針:指向Monitor鎖的指針

【Java面試八股文】Java多線程篇_java多線程八股文_vincewm的博客-CSDN博客

在JVM中,對(duì)象的創(chuàng)建遵循如下過程:

當(dāng)JVM遇到一條字節(jié)碼new指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。

在類加載檢查通過后,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需內(nèi)存的大小在類加載完成后便可完全確定,為對(duì)象分配空間的任務(wù)實(shí)際上便等同于把一塊確定大小的內(nèi)存塊從Java堆中劃分出來。

內(nèi)存分配完成之后,虛擬機(jī)必須將分配到的內(nèi)存空間都初始化為零值,如果使用了TLAB的話,這一項(xiàng)工作也可以提前至TLAB分配時(shí)順便進(jìn)行。這步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用,使程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

接下來,虛擬機(jī)還要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭之中。根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。

在上面工作都完成之后,從虛擬機(jī)的視角來看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了。但是從Java程序的視角看來,對(duì)象創(chuàng)建才剛剛開始——構(gòu)造函數(shù),即Class文件中的`()`方法還沒有執(zhí)行,所有的字段都為默認(rèn)的零值,對(duì)象需要的其他資源和狀態(tài)信息也還沒有按照預(yù)定的意圖構(gòu)造好。

一般來說,new指令之后會(huì)接著執(zhí)行`()`方法,按照程序員的意愿對(duì)對(duì)象進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全被構(gòu)造出來。

5、說說JVM的雙親委派模型

得分點(diǎn)

三個(gè)默認(rèn)類加載器、工作過程、作用

JVM三個(gè)默認(rèn)類加載器

啟動(dòng)類加載器BootStrapClassLoader(最頂端):無法被Java程序直接引用,只能加載委派過來的請(qǐng)求。擴(kuò)展類加載器ExtClassLoader:可以直接用來加載類,也可以通過委派加載類。Ext是Extract縮寫,譯為擴(kuò)展、提取。應(yīng)用程序類加載器AppClassLoader(最低端):負(fù)責(zé)加載類路徑所有的類庫,開發(fā)者同樣可以直接在代碼中使用這個(gè)類加載器。

雙親委派模型的工作過程

雙親委派模型的工作過程是,如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去完成加載。

具體的流程:

JVM在加載一個(gè)類時(shí),會(huì)調(diào)用應(yīng)用程序類加載器的loadClass()方法來加載這個(gè)類,不過在這方法中,會(huì)先使用擴(kuò)展類加載器的loadClass()方法來加載類,同樣擴(kuò)展類加載器的loadClass()方法中會(huì)先使用啟動(dòng)類加載器來加載類;

如果啟動(dòng)類加載器加載到了就直接成功,如果啟動(dòng)類加載器沒有加載到,那擴(kuò)展類加載器就會(huì)自己嘗試加載該類,如果沒有加載到,那么則會(huì)由應(yīng)用程序類加載器來加載這個(gè)類。

作用:

避免類的重復(fù)加載:無論哪一個(gè)類加載器要加載某類,最終都是委派最頂端的啟動(dòng)類加載器。防止核心API被篡改:如果沒有使用雙親委派模型,都由各個(gè)類加載器自行去加載的話,如果用戶自己也編寫了一個(gè)名為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無從保證,應(yīng)用程序?qū)?huì)變得一片混亂。

類路徑:

classpath:類路徑classpath是編譯之后的target文件夾下的WEB-INF/class文件夾。內(nèi)容等同于打包前的src.main.java和src.main.resource下的目錄和文件

classpath* :不僅包含class路徑,還包括jar文件中(class路徑)進(jìn)行查找.?

對(duì)于JDK8及其之前版本的Java應(yīng)用,都會(huì)使用到以下3個(gè)系統(tǒng)提供的類加載器來進(jìn)行加載:

1.啟動(dòng)類加載器

這個(gè)類加載器負(fù)責(zé)加載存放在`\lib`目錄,或者被-Xbootclasspath參數(shù)所指定的路徑中存放的,而且是Java虛擬機(jī)能夠識(shí)別的類庫加載到虛擬機(jī)的內(nèi)存中。注意,Java虛擬機(jī)會(huì)按照文件名識(shí)別類庫,例如rt.jar、tools.jar,對(duì)于名字不符合的類庫即使放在lib目錄中也不會(huì)被加載。啟動(dòng)類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請(qǐng)求委派給引導(dǎo)類加載器去處理,那直接使用null代替即可,即讓java.lang.ClassLoader.getClassLoader()返回null。

2.擴(kuò)展類加載器

這個(gè)類加載器是在類sun.misc.Launcher$ExtClassLoader中以Java代碼的形式實(shí)現(xiàn)的。它負(fù)責(zé)加載`\lib\ext`目錄中,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中所有的類庫。由于擴(kuò)展類加載器是由Java代碼實(shí)現(xiàn)的,開發(fā)者可以直接在程序中使用擴(kuò)展類加載器來加載Class文件。

3.應(yīng)用程序類加載器

這個(gè)類加載器由sun.misc.Launcher$AppClassLoader來實(shí)現(xiàn)。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所有的類庫,開發(fā)者同樣可以直接在代碼中使用這個(gè)類加載器。

用戶還可以加入自定義的類加載器來進(jìn)行拓展,這些類加載器之間的協(xié)作關(guān)系“通?!比缦聢D所示。圖中展示的各種類加載器之間的層次關(guān)系被稱為類加載器的“雙親委派模型”。雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器。不過這里類加載器之間的父子關(guān)系一般不是以繼承的關(guān)系來實(shí)現(xiàn)的,而是通常使用組合關(guān)系來復(fù)用父加載器的代碼。

?

工作過程

雙親委派模型的工作過程是,如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去完成加載。

作用:避免類的重復(fù)加載、防止核心API被篡改

使用雙親委派模型來組織類加載器之間的關(guān)系,一個(gè)顯而易見的好處就是Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放在rt.jar之中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都能夠保證是同一個(gè)類。

反之,如果沒有使用雙親委派模型,都由各個(gè)類加載器自行去加載的話,如果用戶自己也編寫了一個(gè)名為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無從保證,應(yīng)用程序?qū)?huì)變得一片混亂。

加分回答-雙親委派模型的3次被破壞

雙親委派模型并不是一個(gè)具有強(qiáng)制性約束的模型,而是Java設(shè)計(jì)者推薦給開發(fā)者們的類加載器實(shí)現(xiàn)方式。在Java的世界中大部分的類加載器都遵循這個(gè)模型,但也有例外的情況,雙親委派模型主要出現(xiàn)過3次較大規(guī)模的“被破壞”的情況。

1.雙親委派模型的第一次“被破壞”發(fā)生在雙親委派模型出現(xiàn)之前

雙親委派模型在JDK1.2之后才被引入,但是類加載器的概念和抽象類ClassLoader則在Java的第一個(gè)版本中就已經(jīng)存在,面對(duì)已經(jīng)存在的用戶自定義類加載器的代碼,Java設(shè)計(jì)者們引入雙親委派模型時(shí)不得不做出一些妥協(xié)。為了兼容這些已有代碼,只能在之后的ClassLoader中添加一個(gè)protected方法findClass(),并引導(dǎo)用戶編寫的類加載邏輯時(shí)盡可能去重寫這個(gè)方法,而不是在loadClass()中編寫代碼。雙親委派的具體邏輯就實(shí)現(xiàn)在這里面,按照loadClass()的邏輯,如果父類加載失敗,會(huì)自動(dòng)調(diào)用自己的findClass()來完成加載,這樣既不影響用戶按照自己的意愿去加載類,又可以保證新寫出來的類加載器符合雙親委派規(guī)則。

2.雙親委派模型的第二次“被破壞”是由這個(gè)模型自身的缺陷導(dǎo)致的

雙親委派很好地解決了各個(gè)類加載器協(xié)作時(shí)基礎(chǔ)類型的一致性問題,基礎(chǔ)類型之所以被稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a繼承、調(diào)用的API存在,但程序設(shè)計(jì)往往沒有絕對(duì)不變的完美規(guī)則,如果有基礎(chǔ)類型又要調(diào)用回用戶的代碼,那該怎么辦呢?

一個(gè)典型的例子便是JNDI服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標(biāo)準(zhǔn)服務(wù),它的代碼由啟動(dòng)類加載器來完成加載,肯定屬于Java中很基礎(chǔ)的類型了。但JNDI存在的目的就是對(duì)資源進(jìn)行查找和集中管理,它需要調(diào)用由其他廠商實(shí)現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI服務(wù)提供者接口的代碼,現(xiàn)在問題來了,啟動(dòng)類加載器是絕不可能認(rèn)識(shí)、加載這些代碼的,那該怎么辦?

為了解決這個(gè)困境,Java的設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器。

有了線程上下文類加載器,程序就可以做一些“舞弊”的事情了。JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需的SPI服務(wù)代碼,這是一種父類加載器去請(qǐng)求子類加載器完成類加載的行為,這種行為實(shí)際上是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,已經(jīng)違背了雙親委派模型的一般性原則,但也是無可奈何的事情。

3.雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序動(dòng)態(tài)性的追求而導(dǎo)致的

這里所說的“動(dòng)態(tài)性”指的是一些非常“熱”門的名詞:代碼熱替換、模塊熱部署等。說白了就是希望Java應(yīng)用程序能像我們的電腦外設(shè)那樣,接上鼠標(biāo)、U盤,不用重啟機(jī)器就能立即使用,鼠標(biāo)有問題或要升級(jí)就換個(gè)鼠標(biāo),不用關(guān)機(jī)也不用重啟。

早在2008年,在Java社區(qū)關(guān)于模塊化規(guī)范的第一場戰(zhàn)役里,由Sun/Oracle公司所提出的JSR-294、JSR-277規(guī)范提案就曾敗給以IBM公司主導(dǎo)的JSR-291(即OSGi R4.2)提案。盡管Sun/Oracle并不甘心就此失去Java模塊化的主導(dǎo)權(quán),隨即又再拿出Jigsaw項(xiàng)目迎戰(zhàn),但此時(shí)OSGi已經(jīng)站穩(wěn)腳跟,成為業(yè)界“事實(shí)上”的Java模塊化標(biāo)準(zhǔn)。

OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn),每一個(gè)程序模塊(OSGi中稱為Bundle)都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)Bundle時(shí),就把Bundle連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換。在OSGi環(huán)境下,類加載器不再雙親委派模型推薦的樹狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為更加復(fù)雜的網(wǎng)狀結(jié)構(gòu)。

6、說說JVM調(diào)優(yōu)思路

JVM調(diào)優(yōu)三步驟、性能監(jiān)控、性能分析、性能調(diào)優(yōu)

JVM調(diào)優(yōu)三步驟:

監(jiān)控發(fā)現(xiàn)問題工具分析問題性能調(diào)優(yōu)?

監(jiān)控發(fā)現(xiàn)問題:看服務(wù)器有沒有以下情況,有的話需要調(diào)優(yōu):

GC頻繁CPU負(fù)載過高OOM內(nèi)存泄露死鎖程序響應(yīng)時(shí)間較長

工具分析問題:使用分析工具定位oom、內(nèi)存泄漏等問題

調(diào)優(yōu)依據(jù):吞吐量提高的代價(jià)是停頓時(shí)間拉長。如果應(yīng)用程序跟用戶基本不交互,就優(yōu)先提升吞吐量。如果應(yīng)用程序和用戶頻繁交互,就優(yōu)先縮短停頓時(shí)間。GC日志:使用GCViewer、VisualVM、GCeasy等日志分析工具打印GC日志;JDK自帶的命令行調(diào)優(yōu)工具:

jps:查看正在運(yùn)行的 Java 進(jìn)程。jps -v查看進(jìn)程啟動(dòng)時(shí)的JVM參數(shù);jstat:查看指定進(jìn)程的 JVM 統(tǒng)計(jì)信息。jstat -gc查看堆各分區(qū)大小、YGC,FGC次數(shù)和時(shí)長。如果服務(wù)器沒有 GUI 圖形界面,只提供了純文本控制臺(tái)環(huán)境,它是運(yùn)行期定位虛擬機(jī)性能問題的首選工具。jinfo:實(shí)時(shí)查看和修改指定進(jìn)程的 JVM 配置參數(shù)。jinfo -flag查看和修改具體參數(shù)。jstack:打印指定進(jìn)程此刻的線程快照。定位線程長時(shí)間停頓的原因,例如死鎖、等待資源、阻塞。如果有死鎖會(huì)打印線程的互相占用資源情況。

線程快照:該進(jìn)程內(nèi)每條線程正在執(zhí)行的方法堆棧的集合。JDK自帶的可視化監(jiān)控工具:例如jconsole、Visual VM。Visual VM可以監(jiān)視應(yīng)用程序的 CPU、GC、堆、方法區(qū)、線程快照,查看JVM進(jìn)程、JVM 參數(shù)、系統(tǒng)屬性。MAT:解析Heap Dump(堆轉(zhuǎn)儲(chǔ))文件dump.hprof,查看GC Roots、引用鏈、對(duì)象信息、類信息、線程信息。可以快速生成內(nèi)存泄漏報(bào)表。

生成dump文件方式:

jmapJVM參數(shù):OOM后生成、FGC前生成Visual VMMAT直接從Java進(jìn)程導(dǎo)出dump文件

// 開啟在出現(xiàn) OOM 錯(cuò)誤時(shí)生成堆轉(zhuǎn)儲(chǔ)文件 -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError // 將生成的堆轉(zhuǎn)儲(chǔ)文件保存到 /tmp 目錄下,并以進(jìn)程 ID 和時(shí)間戳作為文件名 -XX:HeapDumpPath=/tmp/java_%p_%t.hprof // 在進(jìn)行 Full GC 前生成堆轉(zhuǎn)儲(chǔ)文件 // 注:如果沒有開啟自動(dòng) GC,則此參數(shù)無效。JDK 9 之后該參數(shù)已被刪除。 -XX:+HeapDumpBeforeFullGC

性能調(diào)優(yōu):

排查大對(duì)象和內(nèi)存泄漏:使用MAT分析堆轉(zhuǎn)儲(chǔ)日志中的大對(duì)象,看是否合理。大對(duì)象會(huì)直接進(jìn)入老年代,導(dǎo)致Full GC頻繁。具體排查步驟看下面OOM。調(diào)整JVM參數(shù):主要關(guān)注停頓時(shí)間和吞吐量,兩者不可兼得,提高吞吐量會(huì)拉長停頓時(shí)間。

減少停頓時(shí)間:垃圾收集器做垃圾回收中斷應(yīng)用執(zhí)行的時(shí)間。 可以通過-XX:MaxGCPauseMillis參數(shù)進(jìn)行設(shè)置,以毫秒為單位,至少大于1提高吞吐量:吞吐量=運(yùn)行時(shí)長/(運(yùn)行時(shí)長+GC時(shí)長)。通過-XX:GCTimeRatio=n參數(shù)進(jìn)行設(shè)置,99的話代表吞吐量為99%, 一般吞吐量不能低于95%。吞吐量太高會(huì)拉長停頓時(shí)間,造成用戶體驗(yàn)下降。調(diào)整堆內(nèi)存大小:根據(jù)程序運(yùn)行時(shí)老年代存活對(duì)象大?。ㄓ洖閤)進(jìn)行調(diào)整,整個(gè)堆內(nèi)存大小設(shè)置為X的3~4倍。年輕代占堆內(nèi)存的3/8。

-Xms:初始堆內(nèi)存大小。默認(rèn):物理內(nèi)存小于192MB時(shí),默認(rèn)為物理內(nèi)存的1/2;物理內(nèi)存大192MB且小于128GB時(shí),默認(rèn)為物理內(nèi)存的1/4;物理內(nèi)存大于等于128GB時(shí),都為32GB。-Xmx:最大堆內(nèi)存大小,建議保持和初始堆內(nèi)存大小一樣。因?yàn)閺某跏级训阶畲蠖训倪^程會(huì)有一定的性能開銷,而且現(xiàn)在內(nèi)存不是稀缺資源。-Xmn:年輕代大小。JDK官方建議年輕代占整個(gè)堆大小空間的3/8左右。調(diào)整堆內(nèi)存比例:調(diào)整伊甸園區(qū)和幸存區(qū)比例、新生代和老年代比例。Young GC頻繁時(shí),我們提高新生代比例和伊甸園區(qū)比例。默認(rèn)情況,伊甸園區(qū):S0:S1=8:1:1,新生代:老年代=1:2。調(diào)整升老年代年齡:JDK8時(shí)Young GC默認(rèn)把15歲的對(duì)象移動(dòng)到老年代。JDK9默認(rèn)值改為7。當(dāng)Full GC頻繁時(shí),我們提高升老年齡,讓年輕代的對(duì)象多在年輕代待一會(huì),從而降低Full GC頻率。JDK8默認(rèn)Young GC時(shí)將15歲的對(duì)象移動(dòng)到老年代。調(diào)整大對(duì)象閾值:Young GC時(shí)大對(duì)象會(huì)不顧年齡直接移動(dòng)到老年代。當(dāng)Full GC頻繁時(shí),我們關(guān)閉或提高大對(duì)象閾值,讓老年代更遲填滿。默認(rèn)是0,即大對(duì)象不會(huì)直接在YGC時(shí)移到老年代。調(diào)整GC的觸發(fā)條件:

CMS調(diào)整老年代觸發(fā)回收比例:CMS的并發(fā)標(biāo)記和并發(fā)清除階段是用戶線程和回收線程并發(fā)執(zhí)行,如果老年代滿了再回收會(huì)導(dǎo)致用戶線程被強(qiáng)制暫停。所以我們修改回收條件為老年代的60%,保證回收時(shí)預(yù)留足夠空間放新對(duì)象。CMS默認(rèn)是老年代68%時(shí)觸發(fā)回收機(jī)制。G1調(diào)整存活閾值:超過存活閾值的Region,其內(nèi)對(duì)象會(huì)被混合回收到老年代。G1回收時(shí)也要預(yù)留空間給新對(duì)象。存活閾值默認(rèn)85%,即當(dāng)一個(gè)內(nèi)存區(qū)塊中存活對(duì)象所占比例超過 85% 時(shí),這些對(duì)象就會(huì)通過 Mixed GC 內(nèi)存整理并晉升至老年代內(nèi)存區(qū)域。選擇合適的垃圾回收器:最有效的方式是升級(jí),根據(jù)CPU核數(shù),升級(jí)當(dāng)前版本支持的最新回收器。

CPU單核,那么毫無疑問Serial 垃圾收集器是你唯一的選擇。CPU多核,關(guān)注吞吐量 ,那么選擇Parallel Scavenge+Parallel Old組合(JDK8默認(rèn))。CPU多核,關(guān)注用戶停頓時(shí)間,JDK版本1.6或者1.7,那么選擇ParNew+CMS,吞吐量降低但是低停頓。CPU多核,關(guān)注用戶停頓時(shí)間,JDK1.8及以上,JVM可用內(nèi)存6G以上,那么選擇G1。優(yōu)化業(yè)務(wù)代碼:絕大部分問題都出自代碼。要盡量減少非必要對(duì)象的創(chuàng)建,防止死循環(huán)創(chuàng)建對(duì)象,防止內(nèi)存泄漏,有些情景下需要以時(shí)間換空間,控制內(nèi)存使用增加機(jī)器:增加機(jī)器,分散節(jié)點(diǎn)壓力調(diào)整線程池參數(shù):合理設(shè)置線程池線程數(shù)量緩存、MQ等中間件優(yōu)化:使用中間件提高程序效率,比如緩存、消息隊(duì)列等

JVM參數(shù):?

//調(diào)整內(nèi)存大小 -XX:MetaspaceSize=128m(元空間默認(rèn)大小) -XX:MaxMetaspaceSize=128m(元空間最大大?。? -Xms1024m(堆最大大?。? -Xmx1024m(堆默認(rèn)大?。? -Xmn256m(新生代大小) -Xss256k(棧最大深度大?。? //調(diào)整內(nèi)存比例 //伊甸園:幸存區(qū) -XX:SurvivorRatio=8(伊甸園:幸存區(qū)=8:2) //新生代和老年代的占比 -XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整個(gè)堆的4/5;默認(rèn)值=2 //修改垃圾回收器 //設(shè)置Serial垃圾收集器(新生代) //-XX:+UseSerialGC //設(shè)置PS+PO,新生代使用功能Parallel Scavenge 老年代將會(huì)使用Parallel Old收集器 //-XX:+UseParallelOldGC //CMS垃圾收集器(老年代) //-XX:+UseConcMarkSweepGC //設(shè)置G1垃圾收集器 -XX:+UseG1GC //GC停頓時(shí)間,垃圾收集器會(huì)嘗試用各種手段達(dá)到這個(gè)時(shí)間 -XX:MaxGCPauseMillis //進(jìn)入老年代最小的GC年齡,年輕代對(duì)象轉(zhuǎn)換為老年代對(duì)象最小年齡值,JDK8默認(rèn)值15,JDK9默認(rèn)值7 -XX:InitialTenuringThreshold=7 //新生代可容納的最大對(duì)象,大于則直接會(huì)分配到老年代,0代表沒有限制。 -XX:PretenureSizeThreshold=1000000 //使用多少比例的老年代后開始CMS收集,默認(rèn)是68%,如果頻繁發(fā)生SerialOld卡頓,應(yīng)該調(diào)小 -XX:CMSInitiatingOccupancyFraction //G1混合垃圾回收周期中要包括的舊區(qū)域設(shè)置占用率閾值。默認(rèn)占用率為 65% -XX:G1MixedGCLiveThresholdPercent=65 //Heap Dump(堆轉(zhuǎn)儲(chǔ))文件 //當(dāng)發(fā)生OutOfMemoryError錯(cuò)誤時(shí),自動(dòng)生成堆轉(zhuǎn)儲(chǔ)文件。 -XX:+HeapDumpOnOutOfMemoryError //錯(cuò)誤輸出地址 -XX:HeapDumpPath=/Users/a123/IdeaProjects/java-test/logs/dump.hprof //GC日志 -XX:+PrintGCDetails(打印詳細(xì)GC日志) -XX:+PrintGCTimeStamps:打印GC時(shí)間戳(以基準(zhǔn)時(shí)間的形式) -XX:+PrintGCDateStamps:打印GC時(shí)間戳(以日期格式) -Xlog:gc:(打印gc日志地址)

7、項(xiàng)目中有沒有實(shí)際的JVM調(diào)優(yōu)經(jīng)驗(yàn)?

7.1 CPU飆升?

原因:CPU利用率過高,大量線程并發(fā)執(zhí)行任務(wù)導(dǎo)致CPU飆升。例如鎖等待(例如CAS不斷自旋)、多線程都陷入死循環(huán)、Redis被攻擊、網(wǎng)站被攻擊、文件IO、網(wǎng)絡(luò)IO。

定位步驟:?

定位進(jìn)程ID:通過top命令查看當(dāng)前服務(wù)CPU使用最高的進(jìn)程,獲取到對(duì)應(yīng)的pid(進(jìn)程ID)定位線程ID:使用top -Hp pid,顯示指定進(jìn)程下面的線程信息,找到消耗CPU最高的線程id線程ID轉(zhuǎn)十六進(jìn)制:轉(zhuǎn)十六進(jìn)制是因?yàn)橄乱徊絡(luò)stack打印的線程快照(線程正在執(zhí)行方法的堆棧集合)里線程id是十六進(jìn)制。定位代碼:使用jstack pid | grep tid(十六進(jìn)制),打印線程快照,找到線程執(zhí)行的代碼。一般如果有死鎖的話就會(huì)顯示線程互相占用情況。解決問題:優(yōu)化代碼、增加系統(tǒng)資源(增多服務(wù)器、增大內(nèi)存)。

7.2 GC調(diào)優(yōu)

最差情況下能接受的GC頻率:Young GC頻率10s一次,每次500ms以內(nèi)。Full GC頻率10min一次,每次1s以內(nèi)。

其實(shí)一小時(shí)一次Full GC已經(jīng)算頻繁了,一個(gè)不錯(cuò)的應(yīng)用起碼得控制一天一次Full GC。

監(jiān)控發(fā)現(xiàn)問題:上午8點(diǎn)是我們的業(yè)務(wù)高峰,一到高峰的時(shí)候,用戶感覺到明顯卡頓,監(jiān)控工具(例如Prometheus和Grafana)發(fā)現(xiàn)TP99(99%請(qǐng)求在多少ms內(nèi)完成)時(shí)長明顯變高,有明顯的的毛刺;內(nèi)存使用率也不穩(wěn)定,會(huì)周期性增大再降低,于是懷疑是GC導(dǎo)致。

命令行分析問題:通過jstat -gc觀察服務(wù)器的GC情況,發(fā)現(xiàn)Young GC頻率提高成原來的10倍,F(xiàn)ull GC頻率提高成原來的四倍。正常YGC 10min一次,F(xiàn)GC 10h一次。異常YGC 1min一次,F(xiàn)GC 3h一次;

所以主要問題是Young GC頻繁,進(jìn)而導(dǎo)致Full GC頻繁。Full GC頻繁會(huì)觸發(fā)STW,導(dǎo)致TP99耗時(shí)上升。

解決方案:

排查內(nèi)存泄漏、大對(duì)象、BUG;增大堆內(nèi)存:服務(wù)器加8G內(nèi)存條,同時(shí)提高初始堆內(nèi)存、最大堆內(nèi)存。-Xms、-Xmx。提高新生代比例:新生代和老年代默認(rèn)比例是1:2。-XX:NewRatio=由4改為默認(rèn)的2降低升老年齡:讓存活對(duì)象更快進(jìn)入老年代。-XX:InitialTenuringThreshold=15(JDK8默認(rèn))改成7(JDK9默認(rèn))設(shè)置大對(duì)象閾值:讓大于1M的大對(duì)象直接進(jìn)入老年代。-XX:PretenureSizeThreshold=0(默認(rèn))改為1000000(單位是字節(jié))垃圾回收器升級(jí)為G1:因?yàn)槭荍DK8,所以直接由默認(rèn)的Parallel Scavenge+Parallel Old組合,升級(jí)為低延時(shí)的G1回收器。如果是JDK7版本,不支持G1,可以修改成ParNew+CMS或Parallel Scavenge+CMS,以降低吞吐量為代價(jià)降低停頓時(shí)間。-XX:CMSInitiatingOccupancyFraction降低G1的存活閾值:超過存活閾值的Region,其內(nèi)對(duì)象會(huì)被混合回收到老年代。降低存活閾值,更早進(jìn)入老年代。-XX:G1MixedGCLiveThresholdPercent=90設(shè)為默認(rèn)的85

調(diào)優(yōu)效果:調(diào)優(yōu)后我們重新進(jìn)行了一次壓測,發(fā)現(xiàn)TP99耗時(shí)較之前降低60%。FullGC耗時(shí)降低80%,YoungGC次數(shù)減少30%。TP99耗時(shí)基本持平,完全符臺(tái)預(yù)期。

8、請(qǐng)你說說內(nèi)存溢出

得分點(diǎn)

內(nèi)存溢出、溢出原因、解決方案、mat定位

內(nèi)存溢出:?申請(qǐng)的內(nèi)存大于系統(tǒng)能提供的內(nèi)存。

溢出原因:

本地直接內(nèi)存溢出:本地直接內(nèi)存設(shè)的太小導(dǎo)致溢出。設(shè)置直接內(nèi)存最大值-XX:MaxDirectMemorySize,若不指定則默認(rèn)與Java堆最大值一致。虛擬機(jī)棧和本地方法棧溢出:如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,并且方法遞歸層數(shù)太深時(shí),導(dǎo)致擴(kuò)展棧容量時(shí)無法申請(qǐng)到足夠內(nèi)存。方法區(qū)溢出:運(yùn)行時(shí)生成大量動(dòng)態(tài)類時(shí)會(huì)內(nèi)存溢出。

CGlib動(dòng)態(tài)代理:CGlib動(dòng)態(tài)代理產(chǎn)生大量類填滿了整個(gè)方法區(qū)(方法區(qū)存常量池、類信息、方法信息),直到溢出。CGlib動(dòng)態(tài)代理是在內(nèi)存中構(gòu)建子類對(duì)象實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能擴(kuò)展,如果enhancer.setUseCache(false);,即關(guān)閉用戶緩存,那么每次創(chuàng)建代理對(duì)象都是一個(gè)新的實(shí)例,創(chuàng)建過多就會(huì)導(dǎo)致方法區(qū)溢出。注意JDK動(dòng)態(tài)代理不會(huì)導(dǎo)致方法區(qū)溢出。JSP:大量JSP或動(dòng)態(tài)產(chǎn)生JSP文件的應(yīng)用(JSP第一次運(yùn)行時(shí)需要編譯為Java類)。堆溢出:

死循環(huán)創(chuàng)建過多對(duì)象;集合類中有對(duì)對(duì)象的引用,使用完后未清空,使得JVM不能回收;內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出的數(shù)據(jù)集太大、第三方接口接口傳輸?shù)拇髮?duì)象、接收的MQ消息太大;Tomcat參數(shù)設(shè)置不當(dāng)導(dǎo)致OOM:Tomcat會(huì)給每個(gè)線程創(chuàng)建兩個(gè)默認(rèn)4M大小的緩沖區(qū),高并發(fā)情況下會(huì)導(dǎo)致緩沖區(qū)創(chuàng)建過多,導(dǎo)致OOM。程序計(jì)數(shù)器不會(huì)內(nèi)存溢出。

使用JDK自帶的命令行調(diào)優(yōu)工具?,判斷是否有OOM:

使用jsp命令查看當(dāng)前Java進(jìn)程;使用jstat命令多次統(tǒng)計(jì)GC,比較GC時(shí)長占運(yùn)行時(shí)長的比例;如果比例超過20%,就代表堆壓力已經(jīng)很大了;如果比例超過98%,說明這段時(shí)期內(nèi)幾乎一直在GC,堆里幾乎沒有可用空間,隨時(shí)都可能拋出 OOM 異常。

MAT定位導(dǎo)致OOM:示例代碼:寫死循環(huán)創(chuàng)建對(duì)象,不斷添加到list里,導(dǎo)致堆內(nèi)存溢出;

JVM參數(shù)設(shè)置,內(nèi)存溢出后生成dump文件,設(shè)置路徑;-XX:+HeapDumpOnOutOfMemoryError、-XX:HeapDumpPathMAT解析dump文件;定位大對(duì)象:點(diǎn)擊直方圖圖標(biāo)(Histogram),對(duì)象會(huì)按內(nèi)存大小排序,查看內(nèi)存占用最大的對(duì)象;這個(gè)對(duì)象被誰引用:點(diǎn)擊支配樹(dominator tree),看大對(duì)象被哪個(gè)線程調(diào)用。這里可以看到是被主線程調(diào)用。定位具體代碼:點(diǎn)擊概述圖標(biāo)(thread_overview),看線程的方法調(diào)用鏈和堆棧信息,查看大對(duì)象所屬類和第幾行,定位到具體代碼,解決問題。

解決方案:

通過jinfo命令查看并修改JVM參數(shù),直接增加內(nèi)存。如-Xmx256m檢查錯(cuò)誤日志,查看“OutOfMemory”錯(cuò)誤前是否有其它異?;蝈e(cuò)誤。對(duì)代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況。

標(biāo)準(zhǔn)回答

內(nèi)存溢出,簡單地說內(nèi)存溢出就是指程序運(yùn)行過程中申請(qǐng)的內(nèi)存大于系統(tǒng)能夠提供的內(nèi)存,導(dǎo)致無法申請(qǐng)到足夠的內(nèi)存,于是就發(fā)生了內(nèi)存溢出。

引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:

1. 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);

2. 集合類中有對(duì)對(duì)象的引用,使用完后未清空,使得JVM不能回收;

3. 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對(duì)象實(shí)體;

4. 使用的第三方軟件中的BUG;

5. 啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過小。

加分回答

除了程序計(jì)數(shù)器外,虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)域都有發(fā)生OOM異常的可能。

1. Java堆溢出

Java堆用于儲(chǔ)存對(duì)象實(shí)例,我們只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象,那么隨著對(duì)象數(shù)量的增加,總?cè)萘坑|及最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。

2. 虛擬機(jī)棧和本地方法棧溢出

HotSpot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法棧,如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展棧容量無法申請(qǐng)到足夠的內(nèi)存時(shí),將拋出OutOfMemoryError異常。

3. 方法區(qū)和運(yùn)行時(shí)常量池溢出

方法區(qū)溢出也是一種常見的內(nèi)存溢出異常,在經(jīng)常運(yùn)行時(shí)生成大量動(dòng)態(tài)類的應(yīng)用場景里,就應(yīng)該特別關(guān)注這些類的回收狀況。這類場景常見的包括:程序使用了CGLib字節(jié)碼增強(qiáng)和動(dòng)態(tài)語言、大量JSP或動(dòng)態(tài)產(chǎn)生JSP文件的應(yīng)用、基于OSGi的應(yīng)用等。 在JDK 6或更早之前的HotSpot虛擬機(jī)中,常量池都是分配在永久代中,即常量池是方法區(qū)的一部分,所以上述問題在常量池中也同樣會(huì)出現(xiàn)。而HotSpot從JDK 7開始逐步“去永久代”的計(jì)劃,并在JDK 8中完全使用元空間來代替永久代,所以上述問題在JDK 8中會(huì)得到避免。

4. 本地直接內(nèi)存溢出

直接內(nèi)存的容量大小可通過`-XX:MaxDirectMemorySize`參數(shù)來指定,如果不去指定,則默認(rèn)與Java堆最大值一致。如果直接通過反射獲取Unsafe實(shí)例進(jìn)行內(nèi)存分配,并超出了上述的限制時(shí),將會(huì)引發(fā)OOM異常。

9、請(qǐng)你說說內(nèi)存泄漏

得分點(diǎn)

內(nèi)存泄漏、內(nèi)存泄露的9種情況、性能分析工具判斷是否有內(nèi)存泄漏、解決辦法

內(nèi)存泄漏:?不再使用的對(duì)象仍然被引用,導(dǎo)致GC無法回收;

內(nèi)存泄露的9種情況:

靜態(tài)容器里的對(duì)象:靜態(tài)集合類的生命周期與 JVM 程序一致,容器里的對(duì)象引用也將一直被引用得不到GC;Java里不準(zhǔn)靜態(tài)方法引用非靜態(tài)方法也是防止內(nèi)存泄漏。單例對(duì)象引用的外部對(duì)象:單例模式里,如果單例對(duì)象如果持有外部對(duì)象的引用,因?yàn)閱卫龑?duì)象不會(huì)被回收,那么這個(gè)外部對(duì)象也不會(huì)被回收外部類跟隨內(nèi)部類被引用:內(nèi)部類持有外部類,這個(gè)內(nèi)部類對(duì)象被長期引用了,即使那個(gè)外部類實(shí)例對(duì)象不再被使用,但由于內(nèi)部類持有外部類的實(shí)例對(duì)象,這個(gè)外部類對(duì)象將不會(huì)被垃圾回收,這也會(huì)造成內(nèi)存泄漏。數(shù)據(jù)庫、網(wǎng)絡(luò)、IO等連接忘記關(guān)閉:在對(duì)數(shù)據(jù)庫進(jìn)行操作的過程中,首先需要建立與數(shù)據(jù)庫的連接,當(dāng)不再使用時(shí),需要調(diào)用 close 方法來釋放與數(shù)據(jù)庫的連接。如果對(duì) Connection、Statement 或 ResultSet 不顯性地關(guān)閉,將會(huì)造成大量的對(duì)象無法被回收,從而引起內(nèi)存泄漏。變量作用域不合理:例如一個(gè)變量只會(huì)在某個(gè)方法中使用,卻聲明為成員變量,并且被使用后沒有被賦值為null,將會(huì)導(dǎo)致這個(gè)變量明明已經(jīng)沒用了,生命周期卻還跟對(duì)象一致。HashSet中對(duì)象改變哈希值:當(dāng)一個(gè)對(duì)象被存儲(chǔ)進(jìn) HashSet 集合中以后,就不能修改這個(gè)對(duì)象中的那些參與計(jì)算哈希值的字段了。否則對(duì)象哈希值改變,找不到對(duì)應(yīng)的value。緩存引用忘刪除:一旦你把對(duì)象引用放入到緩存中,他就很容易遺忘,緩存忘了刪除,將導(dǎo)致引用一直存在。邏輯刪除而不是真實(shí)刪除:監(jiān)聽器和其他回調(diào):如果客戶端在你實(shí)現(xiàn)的 API 中注冊回調(diào),卻沒有顯示的取消,那么就會(huì)積聚。需要確?;卣{(diào)立即被當(dāng)作垃圾回收的最佳方法是只保存它的弱引用,例如將他們保存成為 軟WeakHashMap 中的鍵。例如出棧只是移動(dòng)了指針,而沒有將出棧的位置賦值null,導(dǎo)致已出棧的位置還存在引用。線程池時(shí),ThreadLocal忘記remove():使用線程池的時(shí)候,ThreadLocal?需要在使用完線程中的線程變量手動(dòng)?remove(),否則會(huì)內(nèi)存泄漏。因?yàn)榫€程執(zhí)行完后沒有銷毀而是被線程池回收,導(dǎo)致ThreadLocal中的對(duì)象不能被自動(dòng)垃圾回收。?

性能分析工具判斷是否有內(nèi)存泄漏:

JDK自帶的命令行調(diào)優(yōu)工具:

每隔一段較長的時(shí)間通過jstat命令采樣多組 OU(老年代內(nèi)存量) 的最小值;如果這些最小值在上漲,說明無法回收對(duì)象在不斷增加,可能是內(nèi)存泄漏導(dǎo)致的。MAT監(jiān)視診斷內(nèi)存泄漏:

生成堆轉(zhuǎn)儲(chǔ)文件:MAT直接從Java進(jìn)程導(dǎo)出dump文件可疑點(diǎn):查看泄漏懷疑(Leak Suspects),找到內(nèi)存泄漏可疑點(diǎn)可疑線程:可疑點(diǎn)查看詳情(Details),找到可疑線程定位代碼:查看線程調(diào)用棧(See stacktrace),找到問題代碼的具體位置。GC詳細(xì)日志:啟動(dòng)參數(shù)開啟GC詳細(xì)日志,設(shè)置日志地址;-XX:+PrintGCDetails;編譯器警告:查看Eclipse等編譯器的內(nèi)存泄漏警告;Java基準(zhǔn)測試工具:分析代碼性能;?

解決辦法:

牢記內(nèi)存泄漏的場景,當(dāng)一個(gè)對(duì)象不會(huì)被使用時(shí),給它的所有引用賦值null,堤防靜態(tài)容器,記得關(guān)閉連接、別用邏輯刪除,只要用到了引用,變量的作用域要合理。使用java.lang.ref包的弱引用WeakReference,下次垃圾收集器工作時(shí)被回收。檢查代碼;

內(nèi)存泄漏,是指不再使用的對(duì)象仍然被引用,導(dǎo)致垃圾收集器無法回收它們的內(nèi)存。由于不再使用的對(duì)象仍然無法清理,甚至這種情況可能會(huì)越積越多,最終導(dǎo)致致命的OutOfMemoryError。

可以按照如下的思路來分析和解決內(nèi)存泄漏問題:

1. 啟用分析器

Java分析器是通過應(yīng)用程序監(jiān)視和診斷內(nèi)存泄漏的工具,它可以分析我們的應(yīng)用程序內(nèi)部發(fā)生的事情,例如如何分配內(nèi)存。使用分析器,我們可以比較不同的方法并找到可以最佳利用資源的方式。

2. 啟用詳細(xì)垃圾收集日志

通過啟用詳細(xì)垃圾收集日志,我們可以跟蹤GC的詳細(xì)進(jìn)度。要啟用該功能,我們需要將以下內(nèi)容添加到JVM的配置當(dāng)中:`-verbose:gc`。通過這個(gè)參數(shù),我們可以看到GC內(nèi)部發(fā)生的細(xì)節(jié)。

3. 使用引用對(duì)象

我們還可以借助java.lang.ref包內(nèi)置的Java引用對(duì)象來規(guī)避問題,使用java.lang.ref包,而不是直接引用對(duì)象,即使用對(duì)象的特殊引用,使得它們可以輕松地被垃圾收集。

4. Eclipse內(nèi)存泄漏警告

對(duì)于JDK1.5以及更高的版本中,Eclipse會(huì)在遇到明顯的內(nèi)存泄漏情況時(shí)顯示警告和錯(cuò)誤。因此,在Eclipse中開發(fā)時(shí),我們可以定期地訪問“問題”選項(xiàng)卡,并更加警惕內(nèi)存泄漏警告。

5. 基準(zhǔn)測試

我們可以通過執(zhí)行基準(zhǔn)測試來衡量和分析Java代碼的性能。通過這種方式,我們可以比較執(zhí)行相同任務(wù)的替代方法的性能。這可以幫助我們選擇更好的方法,并可以幫助我們節(jié)約內(nèi)存。

6. 代碼審查

最后,我們總是采用經(jīng)典的老方式來進(jìn)行簡單的代碼演練。在某些情況下,即使這種看似微不足道的方法也有助于消除一些常見的內(nèi)存泄漏問題。

加分回答-沒有一刀切的解決方案,具體問題具體分析

通俗地說,我們可以將內(nèi)存泄漏視為一種疾病,它通過阻塞重要的內(nèi)存資源來降低應(yīng)用程序的性能。和所有其他疾病一樣,如果不治愈,隨著時(shí)間的推移,它可能導(dǎo)致致命的應(yīng)用程序崩潰。

內(nèi)存泄漏很難解決,找到它們需要對(duì)Java語言有很深的理解并掌握復(fù)雜的命令。在處理內(nèi)存泄漏時(shí),沒有一刀切的解決方案,因?yàn)樾孤┛赡芡ㄟ^各種不同的事件發(fā)生。 但是,如果我們采用最佳實(shí)踐并定期執(zhí)行嚴(yán)格的代碼演練和分析,那么我們就可以將應(yīng)用程序中內(nèi)存泄漏的風(fēng)險(xiǎn)降到最低。

10、JVM中一次完整的GC流程是怎樣的

堆分為哪幾個(gè)區(qū)、GC流程、注意大對(duì)象和年齡15(JDK8)

首先,任何新對(duì)象都分配到 eden 空間。兩個(gè)幸存者空間開始時(shí)都是空的。當(dāng) eden 空間填滿時(shí),將觸發(fā)一個(gè)Minor GC(年輕代的垃圾回收,也稱為Young GC),刪除所有未引用的對(duì)象,大對(duì)象(需要大量連續(xù)內(nèi)存空間的Java對(duì)象,如那種很長的字符串)直接進(jìn)入老年代。所有被引用的對(duì)象作為存活對(duì)象,將移動(dòng)到第一個(gè)幸存者空間S0,并標(biāo)記年齡為1,即經(jīng)歷過一次Minor GC。之后每經(jīng)過一次Minor GC,年齡+1。GC分代年齡存儲(chǔ)在對(duì)象頭的Mark Word里。當(dāng) eden 空間再次被填滿時(shí),會(huì)執(zhí)行第二次Minor GC,將Eden和S0區(qū)中所有垃圾對(duì)象清除,并將存活對(duì)象復(fù)制到S1并年齡加1,此時(shí)S0變?yōu)榭?。如此反?fù)在S0和S1之間切換幾次之后,還存活的年齡等于15的對(duì)象(JDK8默認(rèn)15,JDK9默認(rèn)7,-XX:InitialTenuringThreshold=7)在下一次Minor GC時(shí)將放到老年代中。?當(dāng)老年代滿了時(shí)會(huì)觸發(fā)Major GC(也稱為Full GC),Major GC 清理整個(gè)堆 – 包括年輕代和老年代。

11、說說JVM的垃圾回收機(jī)制

得分點(diǎn)

新生代收集、老年代收集、混合收集、整堆收集

依據(jù)分代假說理論,垃圾回收可以分為:?新生代收集、老年代收集、混合收集、整堆收集

當(dāng)前商業(yè)虛擬機(jī)的垃圾收集器,大多數(shù)都遵循了“分代收集”的理論進(jìn)行設(shè)計(jì),分代收集名為理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則。而分代收集理論,建立在如下三個(gè)分代假說之上,即弱分代假說、強(qiáng)分代假說、跨代引用假說。依據(jù)分代假說理論,垃圾回收可以分為如下幾類:

1. 新生代收集:目標(biāo)為新生代的垃圾收集。

2. 老年代收集:目標(biāo)為老年代的垃圾收集,目前只有CMS收集器會(huì)有這種行為。

3. 混合收集:目標(biāo)為整個(gè)新生代及部分老年代的垃圾收集,目前只有G1收集器會(huì)有這種行為。

4. 整堆收集:目標(biāo)為整個(gè)堆和方法區(qū)的垃圾收集。

加分回答-垃圾收集器

HotSpot虛擬機(jī)內(nèi)置了很多垃圾收集器,其中針對(duì)新生代的垃圾收集器有Serial、ParNew、Parallel Scavenge,針對(duì)老年代的垃圾收集器有CMS、Serial Old、Parallel Old。此外,HotSpot還內(nèi)置了面向整堆的G1收集器。

在上述收集器中,常見的組合方式有:

1. Serial + Serial Old,是客戶端模式下常用的收集器。

2. ParNew + CMS,是服務(wù)端模式下常用的收集器。

3. Parallel Scavenge + Parallel Old,適用于后臺(tái)運(yùn)算而不需要太多交互的分析任務(wù)。

12、說說GC的可達(dá)性分析算法

得分點(diǎn) 概念、GC Roots、引用鏈、非可達(dá)對(duì)象兩次標(biāo)記

可達(dá)性分析算法:

以根對(duì)象集合(GC Roots)的每個(gè)跟對(duì)象為起始點(diǎn),根據(jù)引用關(guān)系向下搜索,將所有與GC Roots直接或間接有引用關(guān)系的對(duì)象在對(duì)象頭的Mark Word里標(biāo)記為可達(dá)對(duì)象,即不需要回收的有引用關(guān)系對(duì)象。搜索過程所走過的路徑稱為“引用鏈” 。

GC Roots:即GC根節(jié)點(diǎn)集合,是一組必須活躍的引用。可作為GC Roots的對(duì)象:

棧引用的對(duì)象:Java方法棧、本地方法棧中的參數(shù)引用、局部變量引用、臨時(shí)變量引用等。臨時(shí)變量是方法里的中間操作結(jié)果。方法區(qū)中常量、靜態(tài)變量引用的對(duì)象;所有被同步鎖持有的對(duì)象;所有線程對(duì)象;所有跨代引用對(duì)象;JVM內(nèi)部的引用:如基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,常駐的異常對(duì)象,以及應(yīng)用程序類類加載器;?

非可達(dá)對(duì)象被回收需要兩次標(biāo)記:

第一次標(biāo)記后篩選非可達(dá)對(duì)象:第一次被標(biāo)記后,會(huì)進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法,也就是是否有機(jī)會(huì)自救。假如對(duì)象沒有覆蓋或者已被JVM調(diào)用過finalize()方法,也就是說不想自救或已自救過,那么此對(duì)象需要被回收;假如對(duì)象覆蓋并沒被JVM調(diào)用過finalize()方法,該對(duì)象將會(huì)被放置在一個(gè)名為F-Queue的隊(duì)列之中,并在稍后由一條由虛擬機(jī)自動(dòng)建立的、低調(diào)度優(yōu)先級(jí)的Finalizer線程去執(zhí)行它們的finalize()方法。第二次標(biāo)記F-Queue里的未自救對(duì)象:稍后,收集器將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記。如果對(duì)象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己(this)賦值給某個(gè)引用類型的類變量或者對(duì)象的成員變量,那在第二次標(biāo)記時(shí)它將被移出“即將回收”的F-Queue。如果對(duì)象這時(shí)候還沒有逃脫,那基本上它就真的要被回收了。

finalize()方法:?

finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),需要注意的是,任何一個(gè)對(duì)象的finalize()方法都只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次,如果對(duì)象面臨下一次回收,它的finalize()方法不會(huì)被再次執(zhí)行。

另外,finalize()方法的運(yùn)行代價(jià)高昂,不確定性大,無法保證各個(gè)對(duì)象的調(diào)用順序,如今已被官方明確聲明為不推薦使用的語法。

當(dāng)前主流的商用程序語言的內(nèi)存管理子系統(tǒng),都是通過可達(dá)性分析算法來判定對(duì)象是否存活的。

這個(gè)算法的基本思路就是通過一系列稱為“GC Roots”的根對(duì)象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開始,根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路徑稱為“引用鏈”,如果某個(gè)對(duì)象到GC Roots間沒有任何引用鏈相連,或者用圖論的話來說就是從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象是不可能再被使用的。

?

GC Roots?到底是什么東西呢,哪些對(duì)象可以作為 GC Root 呢?

?是一組必須活躍的引用。在Java技術(shù)體系里面,固定可作為GC Roots的對(duì)象包括以下幾種:

在虛擬機(jī)棧中引用的對(duì)象,譬如各個(gè)線程被調(diào)用的方法堆棧中使用到的參數(shù)、局部變量、臨時(shí)變量等;在方法區(qū)中類靜態(tài)屬性引用的對(duì)象,譬如Java類的引用類型靜態(tài)變量;在方法區(qū)中常量引用的對(duì)象,譬如字符串常量池里的引用;在本地方法棧中引用的對(duì)象;JVM內(nèi)部的引用,如基本數(shù)據(jù)類型對(duì)應(yīng)的Class對(duì)象,常駐的異常對(duì)象,以及系統(tǒng)類加載器;所有被同步鎖持有的對(duì)象;反映Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊的回調(diào)、本地代碼緩存等。

加分回答-宣告對(duì)象死亡要經(jīng)歷兩次標(biāo)記 真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過程:1. 第一次標(biāo)記 如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記,隨后進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。假如對(duì)象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,那么虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”。反之,該對(duì)象將會(huì)被放置在一個(gè)名為F-Queue的隊(duì)列之中,并在稍后由一條由虛擬機(jī)自動(dòng)建立的、低調(diào)度優(yōu)先級(jí)的Finalizer線程去執(zhí)行它們的finalize()方法。2. 第二次標(biāo)記 稍后,收集器將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記。如果對(duì)象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己(this)賦值給某個(gè)類變量或者對(duì)象的成員變量,那在第二次標(biāo)記時(shí)它將被移出“即將回收”的集合。如果對(duì)象這時(shí)候還沒有逃脫,那基本上它就真的要被回收了。 ????finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),需要注意的是,任何一個(gè)對(duì)象的finalize()方法都只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次,如果對(duì)象面臨下一次回收,它的finalize()方法不會(huì)被再次執(zhí)行。另外,finalize()方法的運(yùn)行代價(jià)高昂,不確定性大,無法保證各個(gè)對(duì)象的調(diào)用順序,如今已被官方明確聲明為不推薦使用的語法。

13、說說JVM的垃圾回收算法

得分點(diǎn)

標(biāo)記清除、標(biāo)記復(fù)制、標(biāo)記整理,比較優(yōu)缺點(diǎn)(效率、空間浪費(fèi)、調(diào)整引用、stw)、使用場景

標(biāo)記清除算法、標(biāo)記復(fù)制算法、標(biāo)記整理算法。

標(biāo)記清除算法(Mark-Sweep):

標(biāo)記、清除:當(dāng)堆中有效內(nèi)存空間被耗盡時(shí),會(huì)STW(stop the world,暫停其他所有工作線程),然后先標(biāo)記,再清除。標(biāo)記:可達(dá)性分析法,從GC Roots開始遍歷,找到可達(dá)對(duì)象,并在對(duì)象頭中進(jìn)行標(biāo)記。清除:堆內(nèi)存內(nèi)從頭到尾進(jìn)行線性遍歷,“清除”非可達(dá)對(duì)象。注意清除并不是真的置空,垃圾還在原來的位置。實(shí)際是把垃圾對(duì)象的地址維護(hù)在空閑列表,對(duì)象實(shí)例化的申請(qǐng)內(nèi)存階段會(huì)通過空閑列表找到合適大小的空閑內(nèi)存分配給新對(duì)象。優(yōu)點(diǎn):簡單缺點(diǎn):

效率不高:需要可達(dá)性遍歷和線性遍歷,效率差。STW導(dǎo)致用戶體驗(yàn)差:GC時(shí)需要暫停其他所有工作線程,用戶體驗(yàn)差。有內(nèi)存碎片,要維護(hù)空閑列表:回收垃圾對(duì)象后沒有整理,導(dǎo)致堆中出現(xiàn)一塊塊不連續(xù)的內(nèi)存碎片。適用場景:適合小型應(yīng)用程序,內(nèi)存空間不大的情況。應(yīng)用程序越大越不適用這種回收算法。

標(biāo)記復(fù)制算法(Copying) :

標(biāo)記、復(fù)制、清除:將內(nèi)存空間分為兩塊,每次只使用一塊。在進(jìn)行垃圾回收時(shí),先可達(dá)性分析法標(biāo)記可達(dá)對(duì)象,然后將可達(dá)對(duì)象復(fù)制到?jīng)]有被使用的那個(gè)內(nèi)存塊中,最后再清除當(dāng)前內(nèi)存塊中的所有對(duì)象。后續(xù)再按同樣的流程來回復(fù)制和清除。優(yōu)點(diǎn):

垃圾多時(shí)效率高:只需可達(dá)性遍歷,效率很高。無內(nèi)存碎片:因?yàn)橛幸苿?dòng)操作,所以內(nèi)存規(guī)整。缺點(diǎn):

內(nèi)存利用率低,浪費(fèi)內(nèi)存:始終有一半以上的空閑內(nèi)存。需要調(diào)整引用地址:可達(dá)對(duì)象移動(dòng)后,內(nèi)存地址發(fā)生了變化,需要調(diào)整所有引用,指向移動(dòng)后的地址。垃圾少時(shí)效率相對(duì)差,但還是比其他算法強(qiáng):如果可達(dá)對(duì)象比較多,垃圾對(duì)象比較少,那么復(fù)制算法的效率就會(huì)比較低。只為了一點(diǎn)垃圾而移動(dòng)所有對(duì)象未免有些小題大做。所以垃圾對(duì)象多的情況下,復(fù)制算法比較適合。適用場景:適合垃圾對(duì)象多,可達(dá)對(duì)象少的情況,這樣復(fù)制耗時(shí)短。非常適合新生代的垃圾回收,因?yàn)樾律l繁地把可達(dá)對(duì)象從伊甸園區(qū)移動(dòng)到幸存區(qū),而且是新生代滿了適合再M(fèi)inor GC,垃圾對(duì)象占比高,所以回收性價(jià)比非常高,一次通??梢曰厥?0-90%的內(nèi)存空間,現(xiàn)在的商業(yè)虛擬機(jī)都是用這種GC算法回收新生代。

標(biāo)記整理算法(Mark-Compact) :

標(biāo)記、整理、清除:首先可達(dá)性分析法標(biāo)記可達(dá)對(duì)象,然后將可達(dá)對(duì)象按順序整理到內(nèi)存的一端,最后清理邊界外的垃圾對(duì)象。相當(dāng)于內(nèi)存碎片優(yōu)化版的標(biāo)記清楚算法,不用維護(hù)空閑列表。優(yōu)點(diǎn):

無內(nèi)存碎片:內(nèi)存規(guī)整。內(nèi)存利用率最高:內(nèi)存既規(guī)整又不用浪費(fèi)一般空間。缺點(diǎn):

效率最低:效率比其他兩種算法都低需要調(diào)整引用地址:可達(dá)對(duì)象移動(dòng)后,內(nèi)存地址發(fā)生了變化,需要調(diào)整所有引用,指向移動(dòng)后的地址。STW導(dǎo)致用戶體驗(yàn)差:移動(dòng)時(shí)需要暫停其他所有工作線程,用戶體驗(yàn)差。

分代收集算法:將堆分為新生代、老年代不同生命周期的對(duì)象放在不同的代,采用不同的收集算法,以提高回收效率。

引用計(jì)數(shù)法

每個(gè)對(duì)象都保存一個(gè)引用計(jì)數(shù)器屬性,用戶記錄對(duì)象被引用的次數(shù)。

可達(dá)性分析法

可達(dá)性分析法會(huì)以GC Roots作為起始點(diǎn),然后一層一層找到所引用的對(duì)象,被找到的對(duì)象就是存活對(duì)象,那么其他不可達(dá)的對(duì)象就是垃圾對(duì)象。

1. 標(biāo)記清除算法

算法分為“標(biāo)記”和“清除”兩個(gè)階段,首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對(duì)象,也可以反過來,標(biāo)記存活的對(duì)象,統(tǒng)一回收所有未被標(biāo)記的對(duì)象。它主要有如下兩個(gè)缺點(diǎn): 第一個(gè)是執(zhí)行效率不穩(wěn)定,如果Java堆中包含大量對(duì)象,而且其中大部分是需要被回收的,這時(shí)必須進(jìn)行大量標(biāo)記和清除的動(dòng)作,導(dǎo)致標(biāo)記和清除兩個(gè)過程的執(zhí)行效率都隨對(duì)象數(shù)量增長而降低。 第二個(gè)是內(nèi)存空間碎片化問題,標(biāo)記、清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致當(dāng)程序在運(yùn)行過程中需要分配較大對(duì)象時(shí)無法找到足夠的連續(xù)的內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。

2. 標(biāo)記復(fù)制算法

將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。對(duì)于多數(shù)對(duì)象都是可回收的情況,算法需要復(fù)制的就是占少數(shù)的存活對(duì)象,而且每次都是針對(duì)整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時(shí)也就不用考慮有空間碎片的復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配即可。

這種復(fù)制回收算法的代價(jià)是將可用內(nèi)存縮小為了原來的一半,空間浪費(fèi)未免太多了一點(diǎn)。另外,如果內(nèi)存中多數(shù)對(duì)象都是存活的,這種算法將會(huì)產(chǎn)生大量的內(nèi)存間復(fù)制的開銷。所以,現(xiàn)在的商用Java虛擬機(jī)大多都優(yōu)先采用了這種收集算法去回收新生代。

3. 標(biāo)記整理算法

針對(duì)老年代對(duì)象的存亡特征,1974年Edward Lueders提出了另外一種有針對(duì)性的“標(biāo)記-整理”算法,其中的標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向內(nèi)存空間一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。

如果移動(dòng)存活對(duì)象,尤其是在老年代這種每次回收都有大量對(duì)象存活區(qū)域,移動(dòng)存活對(duì)象并更新所有引用這些對(duì)象的地方將會(huì)是一種極為負(fù)重的操作,而且這種對(duì)象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行,像這樣的停頓被最初的虛擬機(jī)設(shè)計(jì)者形象地描述為“Stop The World”。

加分回答

目前,新生代的垃圾回收采用標(biāo)記復(fù)制算法比較多,老年代的垃圾回收采用標(biāo)記整理算法比較多。而標(biāo)記復(fù)制算法浪費(fèi)一半內(nèi)存的缺點(diǎn)長期以來被人詬病,所以業(yè)界也有人針對(duì)該算法給出了改進(jìn)的方案。

IBM公司曾有一項(xiàng)專門研究對(duì)新生代“朝生夕滅”的特點(diǎn)做了更量化的詮釋——新生代中的對(duì)象有98%熬不過第一輪收集。因此并不需要按照1∶1的比例來劃分新生代的內(nèi)存空間。在1989年,Andrew

Appel針對(duì)具備“朝生夕滅”特點(diǎn)的對(duì)象,提出了一種更優(yōu)化的半?yún)^(qū)復(fù)制分代策略,現(xiàn)在稱為“Appel式回收”。

Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor。發(fā)生垃圾搜集時(shí),將Eden和Survivor中仍然存活的對(duì)象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。

HotSpot虛擬機(jī)的Serial、ParNew等新生代收集器均采用了這種策略來設(shè)計(jì)新生代的內(nèi)存布局。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1:1,也即每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(Eden的80%加上一個(gè)Survivor的10%),只有一個(gè)Survivor空間,即10%的新生代是會(huì)被“浪費(fèi)”的。

98%的對(duì)象可被回收僅僅是“普通場景”下測得的數(shù)據(jù),任何人都沒有辦法百分百保證每次回收都只有不多于10%的對(duì)象存活,因此Appel式回收還有一個(gè)充當(dāng)罕見情況的“逃生門”的安全設(shè)計(jì),當(dāng)Survivor空間不足以容納一次Minor

GC之后存活的對(duì)象時(shí),就需要依賴其他內(nèi)存區(qū)域(實(shí)際上大多就是老年代)進(jìn)行分配擔(dān)保。

對(duì)比三種垃圾回收算法:

14、說說七個(gè)垃圾回收器

得分點(diǎn) Serial、Serial Old、PawNew、CMS、Parallel Scavenge、Parallel Old、G1

各版本默認(rèn)回收器:JDK8默認(rèn)回收器是Parallel+Parallel Old。

各區(qū)域?qū)?yīng)算法:?

新生代回收算法:標(biāo)記復(fù)制算法;老年代回收算法:標(biāo)記清除/整理算法整堆回收算法:分區(qū)算法。

Serial(串行收集器):

介紹:單線程、單處理器回收新生代,回收時(shí)會(huì)STW。STW:Stop The World,暫停其他所有工作線程直到收集結(jié)束。算法:標(biāo)記復(fù)制算法回收區(qū)域:新生代優(yōu)點(diǎn):簡單、比其他單線程收集器效率高:單線程,不用線程切換,可以專心進(jìn)行垃圾回收。應(yīng)用場景:適用于內(nèi)存小的桌面應(yīng)用,可以在較短時(shí)間完成收集。Serial GC是最基礎(chǔ)、歷史最悠久的收集器,曾是HotSpot虛擬機(jī)新生代收集器的唯一選擇。命令:指定新生代用Serial GC,老年代用Serial Old GC:-XX:+UseSerialGC

Serial Old(老年代串行收集器):

介紹:Serial收集器的老年代版本。單線程、單處理器回收老年代,回收時(shí)會(huì)STW。算法:標(biāo)記-整理算法

ParNew(并行新生代收集器):Par是Parallel(并行,平行)的縮寫,New:只能處理的是新生代

介紹:Serial收集器的多線程并行版本。多線程并行回收新生代,回收時(shí)會(huì)STW。算法:標(biāo)記復(fù)制算法回收區(qū)域:新生代優(yōu)點(diǎn):多CPU場景下性能高,吞吐量大缺點(diǎn):單CPU場景下性能差,不如串行收集器應(yīng)用場景:多CPU場景下。

Parallel Scavenge(并行收集器):

介紹:可控制高吞吐量,多線程并行回收新生代,回收時(shí)會(huì)STW。比ParNew優(yōu)秀,可以動(dòng)態(tài)調(diào)整內(nèi)存分配情況。算法:標(biāo)記復(fù)制算法回收區(qū)域:新生代應(yīng)用場景:后臺(tái)運(yùn)算量大而不需要太多交互的任務(wù)。JDK8默認(rèn)回收器是Parallel+Parallel Old

Parallel Old(老年代并行收集器):

介紹:Parallel Scavenge收集器的老年代版本。可控制高吞吐量,多線程并行回收老生代,回收時(shí)會(huì)STW。算法:標(biāo)記整理算法回收區(qū)域:老年代

CMS(并發(fā)標(biāo)記清除收集器):

介紹:以最短停頓時(shí)間為目標(biāo),JDK1.5推出,第一次實(shí)現(xiàn)了垃圾收集線程和用戶線程同時(shí)工作。多線程并行回收老生代,低stw。初始標(biāo)記和重新標(biāo)記需要stw,但耗時(shí)很短。算法:標(biāo)記清除算法。不使用標(biāo)記整理算法是為了保證清除時(shí)不影響用戶線程中的工作線程,如果使用標(biāo)記整理算法的話工作線程引用指向的對(duì)象地址就都變了?;厥諈^(qū)域:老年代步驟:

初始標(biāo)記:標(biāo)記GC Roots直接關(guān)聯(lián)的對(duì)象。單線程且停頓用戶線程,速度很快。并發(fā)標(biāo)記:從直接關(guān)聯(lián)對(duì)象并發(fā)遍歷整個(gè)圖,標(biāo)記可達(dá)對(duì)象。并發(fā)不停頓。重新標(biāo)記:修正上一步用戶線程變動(dòng)的標(biāo)記。并發(fā)停頓。速度遠(yuǎn)比并發(fā)標(biāo)記階段快。注意只能修正原有對(duì)象不能修正新增對(duì)象,即只能修正原有對(duì)象非可達(dá)變可達(dá)、可達(dá)變非可達(dá)。并發(fā)清除:并發(fā)線性遍歷并清理未被標(biāo)記的對(duì)象。并發(fā)不停頓。優(yōu)點(diǎn):

并發(fā)速度快;低停頓:用戶線程和垃圾回收器同時(shí)執(zhí)行,僅初始標(biāo)記和重新標(biāo)記階段需要停頓,這兩個(gè)階段運(yùn)行速度很快。缺點(diǎn):

并發(fā)占線程有內(nèi)存碎片:內(nèi)存不規(guī)整,需要維護(hù)空閑列表。無法處理浮動(dòng)垃圾:并發(fā)標(biāo)記階段會(huì)產(chǎn)生新對(duì)象,重新標(biāo)記階段又只能修正不能新增,所以會(huì)出現(xiàn)浮動(dòng)垃圾。回收時(shí)要確保用戶線程有足夠內(nèi)存:不能等老年代滿了再回收,而是內(nèi)存到達(dá)某個(gè)閾值后回收,防止用戶線程在并發(fā)執(zhí)行過程中新創(chuàng)建對(duì)象導(dǎo)致內(nèi)存不夠,導(dǎo)致虛擬機(jī)補(bǔ)償使用Serial Old收集器進(jìn)行回收并處理內(nèi)存碎片,從而浪費(fèi)更多時(shí)間。CMS默認(rèn)是老年代68%時(shí)觸發(fā)回收機(jī)制。-XX:CMSInitiatingOccupancyFraction應(yīng)用場景:因?yàn)榈讓邮菢?biāo)記清除算法,所以有內(nèi)存碎片,適合小應(yīng)用。

??G1(Garbage-First,垃圾優(yōu)先收集器):

介紹:以延遲可控并保證高吞吐量為目標(biāo),為了適應(yīng)內(nèi)存大小和處理器數(shù)量不斷擴(kuò)大而在JDK7推出的垃圾回收器。開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region(區(qū)域)的內(nèi)存布局形式。JDK8支持并發(fā)類卸載后被Oracle官方稱為“全功能的垃圾收集器”。并行低停頓,除了并發(fā)標(biāo)記外需要stw,但耗時(shí)很短(初始標(biāo)記和最終標(biāo)記是真短,篩選回收是有指定STW)。實(shí)現(xiàn)機(jī)制:不再把堆劃分為連續(xù)的分代,而是將堆內(nèi)存分割成2048個(gè)大小相等的Region,各Region根據(jù)需要扮演伊甸園區(qū)、幸存區(qū)、老年代區(qū)、巨大區(qū)。垃圾優(yōu)先收集器跟蹤各Region里垃圾的回收價(jià)值(回收空間大小和預(yù)計(jì)回收時(shí)長),在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間,回收優(yōu)先級(jí)最高的那些Region,以達(dá)到垃圾優(yōu)先的效果。設(shè)置最大停頓時(shí)間:-XX:MaxGCPauseMillis=默認(rèn)0.2sHumongous Region(巨大區(qū)):存儲(chǔ)大小超過Region一半空間的大對(duì)象,如果大對(duì)象的內(nèi)存大小超過了Region大小,將會(huì)被存在幾個(gè)連續(xù)的巨大區(qū)里。G1的大多數(shù)行為把巨大區(qū)看作老年代的一部分。算法:分區(qū)收集算法(整體是標(biāo)記整理算法、Region之間標(biāo)記復(fù)制算法)回收區(qū)域:整堆。整堆里哪個(gè)Region垃圾最多,回收收益最大。步驟:

初始標(biāo)記:標(biāo)記GC Roots直接關(guān)聯(lián)的可達(dá)對(duì)象。單線程且停頓用戶線程,速度很快。并發(fā)標(biāo)記:從直接關(guān)聯(lián)對(duì)象并發(fā)遍歷整個(gè)圖,標(biāo)記可達(dá)對(duì)象。并發(fā)不停頓。最終標(biāo)記:重新標(biāo)記所有存活的對(duì)象和上個(gè)階段用戶線程新產(chǎn)生的可達(dá)對(duì)象。并發(fā)停頓。采用SATB算法,效率比CMS重新標(biāo)記高。并發(fā)停頓。篩選回收:根據(jù)優(yōu)先級(jí)列表,回收價(jià)值高的一些Region,將存活對(duì)象通過標(biāo)記復(fù)制算法復(fù)制到同類型的空閑Region。根據(jù)指定的最大停頓時(shí)間回收,因此可能來不及回收所有垃圾對(duì)象,但能保證回收到最高回收價(jià)值的垃圾。并發(fā)停頓。記憶集:是一個(gè)抽象概念。每個(gè)Region都維護(hù)一個(gè)記憶集Rset,用來記錄其他Region對(duì)象對(duì)本Region對(duì)象的引用。本Region在回收后對(duì)象地址會(huì)改變,用記憶集就能直接知道直接找到對(duì)應(yīng)引用修改指向的地址,從而不用全局掃描??ū恚–ardTable):是記憶集的一種實(shí)現(xiàn)方式??ū硎且粋€(gè)字節(jié)數(shù)組,每個(gè)元素對(duì)應(yīng)一個(gè)內(nèi)存塊,每個(gè)內(nèi)存塊大小都是2^n字節(jié)(Hotspot是2^9=512字節(jié))。寫屏障:當(dāng)前對(duì)象被其他Region對(duì)象通過引用關(guān)系賦值時(shí),賦值前后會(huì)插入寫前屏障和寫后屏障中斷當(dāng)前Region垃圾回收。CMS的記憶集和寫屏障:其他回收器也用到了記憶集和寫后屏障,用來防止回收導(dǎo)致位置改變時(shí),不用為了更正引用地址而掃描整個(gè)堆。例如CMS記憶集記錄老年代指向年輕代的引用。但只有G1用到了寫前屏障。優(yōu)點(diǎn):

無內(nèi)存碎片:因?yàn)檎w和局部是整理和復(fù)制,都不會(huì)產(chǎn)生內(nèi)存碎片。無浮動(dòng)垃圾:最終標(biāo)記階段不但會(huì)修正,也會(huì)標(biāo)記新增對(duì)象。缺點(diǎn):

比CMS更耗費(fèi)內(nèi)存和負(fù)載??赡軄聿患盎厥账欣焊鶕?jù)指定的STW時(shí)間(默認(rèn)0.2s)回收,因此可能來不及回收所有垃圾對(duì)象,但能保證回收到最高回收價(jià)值的垃圾。比CMS更耗費(fèi)內(nèi)存和負(fù)載:因?yàn)槭褂脤懬捌琳虾蛯懞笃琳暇S護(hù)記憶集,而cms只用寫后屏障。應(yīng)用場景:適合多核CPU且內(nèi)存大的大應(yīng)用,小應(yīng)用不及其他回收器,但未來會(huì)越來越適合。

//G1混合垃圾回收周期中要包括的舊區(qū)域設(shè)置占用率閾值。默認(rèn)占用率為 65% -XX:G1MixedGCLiveThresholdPercent=65

標(biāo)準(zhǔn)回答?

《Java虛擬機(jī)規(guī)范》中對(duì)垃圾收集器應(yīng)該如何實(shí)現(xiàn)并沒有做出任何規(guī)定,因此不同的廠商、不同版本的虛擬機(jī)所包含的垃圾收集器都可能會(huì)有很大差別,不同的虛擬機(jī)一般也都會(huì)提供各種參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)內(nèi)存分代所使用的收集器。下圖是HotSpot虛擬機(jī)中包含的垃圾收集器,圖中展示了七種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說明它們可以搭配使用,圖中收集器所處的區(qū)域,則表示它是屬于新生代收集器抑或是老年代收集器。

?

串行收集器Serial

Serial收集器是最基礎(chǔ)、歷史最悠久的收集器,使用復(fù)制算法,曾經(jīng)是HotSpot虛擬機(jī)新生代收集器的唯一選擇。這個(gè)收集器是一個(gè)單線程工作的收集器,但它的“單線程”的意義并不僅僅是說明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作,更重要的是強(qiáng)調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束。也就是說它在進(jìn)行垃圾收集時(shí),會(huì)發(fā)生“Stop The World”

老年代串行收集器Serial Old Serial Old是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用標(biāo)記-整理算法,這個(gè)收集器的主要意義也是供客戶端模式下的HotSpot虛擬機(jī)使用。

ParNew收集器

ParNew收集器實(shí)質(zhì)上是Serial收集器的多線程并行版本,除了同時(shí)使用多條線程進(jìn)行垃圾收集之外,其余的行為包括Serial收集器可用的所有控制參數(shù)、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等都與Serial收集器完全一致,在實(shí)現(xiàn)上這兩種收集器也共用了相當(dāng)多的代碼。

Parallel Scavenge收集器 Parallel Scavenge收集器也是一款新生代收集器,它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器,也是能夠并行收集的多線程收集器。它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量。

Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實(shí)現(xiàn)。直到Parallel Old收集器出現(xiàn)后,“吞吐量優(yōu)先”收集器終于有了比較名副其實(shí)的搭配組合,在注重吞吐量或者處理器資源較為稀缺的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組合。

CMS CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,從名字上就可以看出CMS收集器是基于標(biāo)記-清除算法實(shí)現(xiàn)的,它的運(yùn)作過程相對(duì)于前面幾種收集器來說要更復(fù)雜一些,整個(gè)過程分為四個(gè)步驟,包括:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除。

G1 Garbage First(簡稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果,它開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。到了JDK 8 Update 40的時(shí)候,G1提供并發(fā)的類卸載的支持,補(bǔ)全了其計(jì)劃功能的最后一塊拼圖。這個(gè)版本以后的G1收集器才被Oracle官方稱為“全功能的垃圾收集器”。加分回答 通常,Serial收集器搭配Serial Old使用,ParNew收集器搭配CMS使用,Parallel Scavenge收集器搭配Parallel Old使用。此外,G1是整堆收集器,它無需搭配其他的垃圾收集器。

15、請(qǐng)你講下CMS(并發(fā)標(biāo)記清除)回收器

得分點(diǎn)

介紹、算法、回收區(qū)域、四個(gè)步驟、優(yōu)缺點(diǎn)(并發(fā)、停頓、內(nèi)存碎片、浮動(dòng)垃圾、回收條件)、應(yīng)用場景

CMS(并發(fā)標(biāo)記清除收集器):

介紹:以最短停頓時(shí)間為目標(biāo),JDK1.5推出,第一次實(shí)現(xiàn)了垃圾收集線程和用戶線程同時(shí)工作。多線程并行回收老生代,低stw。初始標(biāo)記和重新標(biāo)記需要stw,但耗時(shí)很短。算法:標(biāo)記清除算法。不使用標(biāo)記整理算法是為了保證清除時(shí)不影響用戶線程中的工作線程,如果使用標(biāo)記整理算法的話工作線程引用指向的對(duì)象地址就都變了?;厥諈^(qū)域:老年代步驟:

初始標(biāo)記:標(biāo)記GC Roots直接關(guān)聯(lián)的對(duì)象。單線程且停頓用戶線程,速度很快。并發(fā)標(biāo)記:從直接關(guān)聯(lián)對(duì)象并發(fā)遍歷整個(gè)圖,標(biāo)記可達(dá)對(duì)象。并發(fā)不停頓。重新標(biāo)記:修正上一步用戶線程變動(dòng)的標(biāo)記。并發(fā)停頓。速度遠(yuǎn)比并發(fā)標(biāo)記階段快。注意只能修正原有對(duì)象不能修正新增對(duì)象,即只能修正原有對(duì)象非可達(dá)變可達(dá)、可達(dá)變非可達(dá)。并發(fā)清除:并發(fā)線性遍歷并清理未被標(biāo)記的對(duì)象。并發(fā)不停頓。優(yōu)點(diǎn):

并發(fā)速度快;低停頓:用戶線程和垃圾回收器同時(shí)執(zhí)行,僅初始標(biāo)記和重新標(biāo)記階段需要停頓,這兩個(gè)階段運(yùn)行速度很快。缺點(diǎn):

并發(fā)占線程拖慢速度有內(nèi)存碎片:內(nèi)存不規(guī)整,需要維護(hù)空閑列表。無法處理浮動(dòng)垃圾:并發(fā)標(biāo)記階段會(huì)產(chǎn)生新對(duì)象,重新標(biāo)記階段又只能修正不能新增,所以會(huì)出現(xiàn)浮動(dòng)垃圾?;厥諘r(shí)要確保用戶線程有足夠內(nèi)存:不能等老年代滿了再回收,而是內(nèi)存到達(dá)某個(gè)閾值后回收,防止用戶線程在并發(fā)執(zhí)行過程中新創(chuàng)建對(duì)象導(dǎo)致內(nèi)存不夠,導(dǎo)致虛擬機(jī)補(bǔ)償使用Serial Old收集器進(jìn)行回收并處理內(nèi)存碎片,從而浪費(fèi)更多時(shí)間。CMS默認(rèn)是老年代68%時(shí)觸發(fā)回收機(jī)制。-XX:CMSInitiatingOccupancyFraction應(yīng)用場景:因?yàn)榈讓邮菢?biāo)記清除算法,所以有內(nèi)存碎片,適合小應(yīng)用。

?CMS收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,從名字上就可以看出CMS收集器是基于標(biāo)記清除算法實(shí)現(xiàn)的,它的運(yùn)作過程相對(duì)于前面幾種收集器來說要更復(fù)雜一些,整個(gè)過程分為四個(gè)步驟,包括:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除。其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”。?

STW:Stop-The-World是在垃圾回收算法執(zhí)行過程中,將jvm內(nèi)存凍結(jié),停頓的一種狀態(tài)。即暫停用戶線程。

?1. 初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快。

?2. 并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程,這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行。

?3. 重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短。

?4. 并發(fā)清除階段,清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象,由于不需要移動(dòng)存活對(duì)象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。

指定老年代使用CMS GC:

-XX:+UseConcMarkSweepGC

加分回答-優(yōu)缺點(diǎn)

CMS是一款優(yōu)秀的收集器,它最主要的優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來:并發(fā)收集、低停頓(單位時(shí)間內(nèi)占用用戶線程的時(shí)間更少了),一些官方公開文檔里面也稱之為“并發(fā)低停頓收集器”。

CMS收集器是HotSpot虛擬機(jī)追求低停頓的第一次成功嘗試,但是它還遠(yuǎn)達(dá)不到完美的程度,至少有以下三個(gè)明顯的缺點(diǎn):

?1. 并發(fā)階段,它雖然不會(huì)導(dǎo)致用戶線程停頓,卻因?yàn)檎加靡徊糠志€程而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量。

?2. 它無法處理“浮動(dòng)垃圾”,有可能會(huì)出現(xiàn)“并發(fā)失敗”進(jìn)而導(dǎo)致另一次Full GC的發(fā)生。

?3. 它是一款基于標(biāo)記清除算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會(huì)有大量內(nèi)存碎片產(chǎn)生。

16、請(qǐng)你講下G1垃圾優(yōu)先回收器

得分點(diǎn)

整堆、Region、標(biāo)記整理、四個(gè)步驟

???G1(Garbage-First,垃圾優(yōu)先收集器):

介紹:以延遲可控并保證高吞吐量為目標(biāo),為了適應(yīng)內(nèi)存大小和處理器數(shù)量不斷擴(kuò)大而在JDK7推出的垃圾回收器。開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region(區(qū)域)的內(nèi)存布局形式。JDK8支持并發(fā)類卸載后被Oracle官方稱為“全功能的垃圾收集器”。并行低停頓,除了并發(fā)標(biāo)記外需要stw,但耗時(shí)很短(初始標(biāo)記和最終標(biāo)記是真短,篩選回收是有指定STW)。實(shí)現(xiàn)機(jī)制:不再把堆劃分為連續(xù)的分代,而是將堆內(nèi)存分割成2048個(gè)大小相等的Region,各Region根據(jù)需要扮演伊甸園區(qū)、幸存區(qū)、老年代區(qū)、巨大區(qū)。垃圾優(yōu)先收集器跟蹤各Region里垃圾的回收價(jià)值(回收空間大小和預(yù)計(jì)回收時(shí)長),在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間,回收優(yōu)先級(jí)最高的那些Region,以達(dá)到垃圾優(yōu)先的效果。設(shè)置最大停頓時(shí)間:-XX:MaxGCPauseMillis=默認(rèn)0.2sHumongous Region(巨大區(qū)):存儲(chǔ)大小超過Region一半空間的大對(duì)象,如果大對(duì)象的內(nèi)存大小超過了Region大小,將會(huì)被存在幾個(gè)連續(xù)的巨大區(qū)里。G1的大多數(shù)行為把巨大區(qū)看作老年代的一部分。算法:分區(qū)收集算法(整體是標(biāo)記整理算法、Region之間標(biāo)記復(fù)制算法)回收區(qū)域:整堆。整堆里哪個(gè)Region垃圾最多,回收收益最大。步驟:

初始標(biāo)記:標(biāo)記GC Roots直接關(guān)聯(lián)的可達(dá)對(duì)象。單線程且停頓用戶線程,速度很快。并發(fā)標(biāo)記:從直接關(guān)聯(lián)對(duì)象并發(fā)遍歷整個(gè)圖,標(biāo)記可達(dá)對(duì)象。并發(fā)不停頓。最終標(biāo)記:重新標(biāo)記所有存活的對(duì)象。并發(fā)停頓。采用SATB算法,效率比CMS重新標(biāo)記高。并發(fā)停頓。篩選回收:根據(jù)優(yōu)先級(jí)列表,回收價(jià)值高的一些Region,將存活對(duì)象通過標(biāo)記復(fù)制算法復(fù)制到同類型的空閑Region。根據(jù)指定的最大停頓時(shí)間回收,因此可能來不及回收所有垃圾對(duì)象,但能保證回收到最高回收價(jià)值的垃圾。并發(fā)停頓。記憶集:是一個(gè)抽象概念。每個(gè)Region都維護(hù)一個(gè)記憶集Rset,用來記錄其他Region對(duì)象對(duì)本Region對(duì)象的引用。本Region在回收后對(duì)象地址會(huì)改變,用記憶集就能直接知道直接找到對(duì)應(yīng)引用修改指向的地址,從而不用全局掃描。卡表(CardTable):是記憶集的一種實(shí)現(xiàn)方式??ū硎且粋€(gè)字節(jié)數(shù)組,每個(gè)元素對(duì)應(yīng)一個(gè)內(nèi)存塊,每個(gè)內(nèi)存塊大小都是2^n字節(jié)(Hotspot是2^9=512字節(jié))。寫屏障:當(dāng)前對(duì)象被其他Region對(duì)象通過引用關(guān)系賦值時(shí),賦值前后會(huì)插入寫前屏障和寫后屏障中斷當(dāng)前Region垃圾回收。CMS的記憶集和寫屏障:其他回收器也用了記憶集和寫后屏障,用來防止回收導(dǎo)致位置改變時(shí),不用為了更正引用地址而掃描整個(gè)堆。例如CMS記憶集記錄老年代指向年輕代的引用。但只有G1用到了寫前屏障。優(yōu)點(diǎn):

無內(nèi)存碎片:因?yàn)檎w和局部是整理和復(fù)制,都不會(huì)產(chǎn)生內(nèi)存碎片。無浮動(dòng)垃圾:最終標(biāo)記階段不但會(huì)修正,也會(huì)標(biāo)記新增對(duì)象。缺點(diǎn):

比CMS更耗費(fèi)內(nèi)存和負(fù)載。可能來不及回收所有垃圾:根據(jù)指定的STW時(shí)間(默認(rèn)0.2s)回收,因此可能來不及回收所有垃圾對(duì)象,但能保證回收到最高回收價(jià)值的垃圾。比CMS更耗費(fèi)內(nèi)存和負(fù)載:因?yàn)槭褂脤懬捌琳虾蛯懞笃琳暇S護(hù)記憶集,而cms只用寫后屏障。應(yīng)用場景:適合多核CPU且內(nèi)存大的大應(yīng)用,小應(yīng)用不及其他回收器,但未來會(huì)越來越適合。

//G1混合垃圾回收周期中要包括的舊區(qū)域設(shè)置占用率閾值。默認(rèn)占用率為 65% -XX:G1MixedGCLiveThresholdPercent=65

Garbage First(G1)垃圾優(yōu)先收集器開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于Region的內(nèi)存布局形式。在G1收集器出現(xiàn)之前的所有其他收集器,垃圾收集的目標(biāo)范圍要么是整個(gè)新生代,要么就是整個(gè)老年代,再要么就是整個(gè)Java堆。而G1跳出了這個(gè)限制,它可以面向堆內(nèi)存任何部分來組成回收集進(jìn)行回收,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大,這就是G1收集器的Mixed GC模式。

G1也仍是遵循分代收集理論設(shè)計(jì)的,但其堆內(nèi)存的布局與其他收集器有非常明顯的差異:G1不再堅(jiān)持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),每一個(gè)Region都可以根據(jù)需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。

巨大區(qū):此外,還有一類專門用來存儲(chǔ)大對(duì)象的特殊區(qū)域(Humongous Region)。G1認(rèn)為只要超過了Region一半的對(duì)象即可判定為大對(duì)象。而對(duì)于那些超過了整個(gè)Region容量的超級(jí)大對(duì)象,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region之中,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分來進(jìn)行看待。

更具體的處理思路是,讓G1收集器去跟蹤各個(gè)Region里面的垃圾堆積的“價(jià)值”大小(垃圾數(shù)量),價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間,優(yōu)先處理回收價(jià)值收益最大的那些Region,這也就是“Garbage First”名字的由來。

G1收集器的運(yùn)作過程大致可劃分為以下四個(gè)步驟:初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收。其中,初始標(biāo)記和最終標(biāo)記階段仍然需要停頓所有的線程,但是耗時(shí)很短。

加分回答-G1與CMS的對(duì)比:

G1從整體來看是基于標(biāo)記整理算法實(shí)現(xiàn)的收集器,但從局部上看又是基于標(biāo)記復(fù)制算法實(shí)現(xiàn)。無論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,垃圾收集完成之后能提供規(guī)整的可用內(nèi)比起CM存。S,G1的弱項(xiàng)也可以列舉出不少。例如在用戶程序運(yùn)行過程中,G1無論是為了垃圾收集產(chǎn)生的內(nèi)存占用還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載都要比CMS要高。

G1與CMS的選擇:

目前在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率仍然要會(huì)優(yōu)于G1,而在大內(nèi)存應(yīng)用上G1則大多能發(fā)揮其優(yōu)勢,這個(gè)優(yōu)劣勢的Java堆容量平衡點(diǎn)通常在6GB至8GB之間。以上這些也僅是經(jīng)驗(yàn)之談,隨著HotSpot的開發(fā)者對(duì)G1的不斷優(yōu)化,也會(huì)讓對(duì)比結(jié)果繼續(xù)向G1傾斜。

G1比CMS更耗費(fèi)內(nèi)存和負(fù)載:因?yàn)槭褂脤懬捌琳虾蛯懞笃琳暇S護(hù)記憶集,而cms只用寫后屏障。

柚子快報(bào)邀請(qǐng)碼778899分享:java JVM概念

http://yzkb.51969.com/

文章鏈接

評(píng)論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://m.gantiao.com.cn/post/19555411.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問

文章目錄