學(xué)習(xí)記錄之JVM總結(jié)
1.1:為什么要學(xué)習(xí)JVM?
????為了面試(企業(yè)招聘戰(zhàn)略在升級(jí),業(yè)務(wù)和技術(shù)問的越來越深)
????為了更好的理解JAVA
????為了更好的解決線上問題
????????實(shí)現(xiàn)線上軟件升級(jí)(熱替換)
????????更好的防止內(nèi)存泄露,提高內(nèi)存的有效使用率
????????更好提高系統(tǒng)的吞吐量...
1.2:你了解那些JVM產(chǎn)品?
????JVM是一個(gè)虛擬機(jī)規(guī)范,基于這個(gè)規(guī)范有不同的規(guī)范,例如:Oracle公司的HotSpot、IBM公司我的J9、阿里公司的TaobaoVM。等
1.3:JVM構(gòu)成有哪幾部分?
????在JVM規(guī)范中,JVM的構(gòu)成,主要由如下幾部分構(gòu)成
????類加載子系統(tǒng)(負(fù)責(zé)將類讀取到內(nèi)存,校驗(yàn)類的合法性,對(duì)類進(jìn)行初始化)
????運(yùn)行時(shí)數(shù)據(jù)區(qū)(負(fù)責(zé)存儲(chǔ)類信息-方法區(qū),對(duì)象信息-堆或棧,執(zhí)行邏輯-棧)
????執(zhí)行引擎(負(fù)責(zé)從指定地址對(duì)應(yīng)的內(nèi)存中讀取數(shù)據(jù)然后解釋執(zhí)行以及GC操作)
????本地庫接口(負(fù)責(zé)實(shí)現(xiàn)Java語言與其他編程語言之間的協(xié)同)
2類加載部分
?2.1:類如何加載?
????在JVM中類的加載由類加載器負(fù)責(zé),他負(fù)責(zé)將類從指定位置讀到內(nèi)存,并進(jìn)行校驗(yàn)和類變量初始化。
2.2:你知道那些類加載器
????JDK的hotspot虛擬機(jī)中定義了如下幾種類型的類加載器
????第一:BootStrapClassLoader(根類加載器,負(fù)責(zé)加載JVM基礎(chǔ)類庫中的類)
????第二:ExtClassLoder(擴(kuò)展類加載器,負(fù)責(zé)加載JVM擴(kuò)展包中的類)
????第三:AppClassLoader(我們自己寫的類的類加載器,負(fù)責(zé)從classpath路徑下加載類)
????第四:自定義ClassLoader(自定義類加載器,可以指定類加載方式以及擴(kuò)展類的加載路徑)

2.3:為什么需要多個(gè)類加載器
每個(gè)類加載器都有自己的加載職責(zé),負(fù)責(zé)從不同位置加載我們所需要的類,同時(shí)可以基于需求進(jìn)行懶加載(按需加載),例如:
????加載基礎(chǔ)類庫(核心類庫)
????擴(kuò)展類庫
????三方類(MyBatis,Spring,...)
????自己的類
2.4:雙親委派方式加載類有什么優(yōu)勢(shì)、劣勢(shì)
????通過雙親委派模型可以簡單理解為向上新聞,向下委托。當(dāng)我們的類在被加載時(shí),首先會(huì)詢問類加載器對(duì)象的parent對(duì)象(兩者不是繼承關(guān)系),是否已經(jīng)加載過此類,假如當(dāng)前parent沒有加載過此類,則會(huì)繼續(xù)向上詢問他的parent,一次遞歸(一直到根類加載器)。如果當(dāng)前父加載器可以完成類加載則直接加載,假如不可以則委托給下一層類加載器區(qū)加載(可以理解為逐層分配任務(wù))

2.5:描述一下類加載時(shí)候的基本步驟是怎樣的?
????通過雙親委派加載機(jī)制,保證同一個(gè)類只能被加載一次,同時(shí)也是堆類資源的一種保護(hù),例如我們自己也寫了一個(gè)java.lang.object類,為了保證Java官方的java.lang.object類加載后不再加載我們的Object就可以使用雙親委派機(jī)制,但是這里也有一個(gè)缺陷,例如我們同一個(gè)JVM下有多個(gè)項(xiàng)目,但是不同項(xiàng)目中有包名類名都相同的類(類內(nèi)容是不同的),此時(shí)只能有一個(gè)項(xiàng)目中的類會(huì)被加載,其他項(xiàng)目則無法加載,還有這種雙清委派模型可能會(huì)因?yàn)橄蛏显儐栂蛳挛?,多少?huì)影響一些性能。
2.6:描述一些類加載時(shí)候的基本步驟是怎樣的?
????1查找類(例如通過指定路徑+類全名找到指定類)
????2讀取類(通過字節(jié)輸入流讀取類到內(nèi)存,并將類信息存儲(chǔ)到字節(jié)數(shù)組)
????3對(duì)字節(jié)數(shù)組中的信息流進(jìn)行校驗(yàn)分析以及初始化將其結(jié)構(gòu)存儲(chǔ)到方法區(qū)
????4堆字節(jié)碼對(duì)象(java.lang.Class),基于對(duì)象封裝類信息的引用,基于這些引用獲取方法區(qū)類信息,
2.7:什么情況下回觸發(fā)類的加載
????我們可以將類的加載分為顯示加載和隱世加載,顯示加載是通過類加載器的loadClass方法或Class類的forName方法
直接對(duì)類進(jìn)行加載,隱式加載一般指構(gòu)建對(duì)象、訪問類中屬性或方法時(shí)觸發(fā)的類加載。
2.8:類加載時(shí)靜態(tài)代碼塊一定會(huì)執(zhí)行嗎?
????不一定,靜態(tài)代碼塊是否會(huì)執(zhí)行,取決于類加載時(shí),是否會(huì)執(zhí)行類的初始化
2.9:如何理解類的主動(dòng)加載和被動(dòng)加載
????主動(dòng)加載:訪問本類成員或方法時(shí)觸發(fā)的加載(但是基于類型定義變量不會(huì)觸發(fā)類的加載)
????被動(dòng)加載:訪問本類(當(dāng)前類)對(duì)應(yīng)的父類屬性時(shí),本類(當(dāng)前類)屬于被動(dòng)加載,被動(dòng)加載不會(huì)觸發(fā)當(dāng)前類的初始化。
2.10:為什么要自定義類加載器,如何定義?
????當(dāng)系統(tǒng)提供的類加載器不能完全滿足我們的需求時(shí),我們可以考慮自定義類加載器,例如:
????指定加載源頭(系統(tǒng)提供的類加載器已經(jīng)確定了從那些位置加載對(duì)應(yīng)類,假如我們的類不在指定范圍內(nèi)呢?)
????保證類安全?(可以在類編譯時(shí)堆字節(jié)碼進(jìn)行加密,類加載器對(duì)字節(jié)碼進(jìn)行解密)
????打破雙親委派模型?(同一個(gè)JVM有多個(gè)項(xiàng)目時(shí),假如不同項(xiàng)目中有相同名字的類,這些類都需要加載)
2.11:內(nèi)存中一個(gè)類的字節(jié)碼對(duì)象可以有多個(gè)嗎?
????可以,即時(shí)是同一個(gè)類,但是它的類加載器不同,生成的類加載器不同,生成的字節(jié)碼對(duì)象也不會(huì)相同。
JVM運(yùn)行內(nèi)存部分
3.1:JVM運(yùn)行內(nèi)存是如何劃分的?
????JVM運(yùn)行時(shí)內(nèi)存有方法區(qū)(Method Area)、堆區(qū)(Heap)、Java方法棧(Stack)、本地方法棧,程序計(jì)數(shù)器(寄存器).
3.2:JVM中程序計(jì)數(shù)器用于做什么?
????Java中每個(gè)線程都有一個(gè)程序計(jì)數(shù)器,為線程私有,用于記錄程序執(zhí)行時(shí)的字節(jié)碼指令地址
3.3:JVM虛擬機(jī)棧的結(jié)構(gòu)是怎樣的?
????Java中每個(gè)線程有一個(gè)虛擬機(jī)棧(Java方法棧),每個(gè)方法的執(zhí)行和退出對(duì)應(yīng)這一次入棧(Push)和出棧(pop)操作。這個(gè)棧中的元素為一個(gè)一個(gè)的棧幀對(duì)象,這個(gè)棧幀對(duì)象有如下幾部分構(gòu)成
????操作數(shù)棧(用于執(zhí)行運(yùn)算,例如兩個(gè)變量值的加減)
????局部變量表(用于存儲(chǔ)方法內(nèi)部的局部變量對(duì)于實(shí)例方法,局部變量表的第0個(gè)位置為this)
????方法返回值(記錄調(diào)用方法的返回值)
????動(dòng)態(tài)連接(方法中可以調(diào)用其他方法,如何找到調(diào)用的方法?)
????其他信息
3.4:JVM虛擬機(jī)棧中局部變量表的作用什么?
????局部變量表底層實(shí)現(xiàn)是一個(gè)數(shù)組,用于存儲(chǔ)方法內(nèi)的局部變量,對(duì)于main方法而言,方法中args這個(gè)變量會(huì)存儲(chǔ)在局部變量表下標(biāo)為0的位置,對(duì)于實(shí)例方法,局部變量下標(biāo)為0位置存儲(chǔ)的是this
3.5:JVM虛擬機(jī)棧中操作數(shù)棧的作用什么?
????最核心的作用是進(jìn)行計(jì)算,JVM的執(zhí)行引擎可以基礎(chǔ)程序及計(jì)算器中指令的地址找到具體指令,然后執(zhí)行。在執(zhí)行這些指令時(shí),可以將指令對(duì)應(yīng)的數(shù)據(jù)放到操作數(shù)棧,也可以從操作數(shù)棧將數(shù)據(jù)取出存儲(chǔ)到局部變量表,還可以將局部變量表中的數(shù)據(jù)取出,進(jìn)行計(jì)算,將計(jì)算的結(jié)構(gòu)在存儲(chǔ)到操作數(shù)棧中。
3.6:JVM堆的構(gòu)成是怎樣的?
????JVN堆主要用于存儲(chǔ)我們創(chuàng)建Java對(duì)象。這個(gè)堆由年輕代(Young區(qū))和老年代(old區(qū))構(gòu)成,年輕帶又分伊甸園區(qū)和兩個(gè)幸存區(qū)(其中一個(gè)幸存區(qū)始終是空閑的,堆年輕帶進(jìn)行GC時(shí),活著的對(duì)象會(huì)拷貝到空著的幸存區(qū),幸存區(qū)無法存儲(chǔ)這個(gè)要拷貝的對(duì)象,對(duì)象還可以直接進(jìn)行老年代)
3.7:Java對(duì)象分配內(nèi)存的過程是怎樣的?
????編譯器通過逃逸分析(JDK8默認(rèn)開啟),確定對(duì)象是在棧上分配還是在堆上分配。
????如果是在堆上分配,則首先檢測(cè)是否可在TLAB(Thread Local Allocation Buffer)上直接分配
????如果TLAB上無法直接分配則在Eden加鎖區(qū)(CAS算法進(jìn)行加鎖)進(jìn)行分配(線程共享區(qū))
????如果Eden區(qū)無法存儲(chǔ)對(duì)象,則執(zhí)行yong GC(Minor Collertion -小GC)
????如果Yong GC之后Eden區(qū)仍然不足以存儲(chǔ)對(duì)象,則直接分配在老年代(當(dāng)老年代內(nèi)存不足時(shí)就會(huì)出現(xiàn)fullgc)
3.8:JVM年輕代幸存區(qū)設(shè)置的比較小會(huì)有什么問題
????伊甸園區(qū)被回收時(shí),對(duì)象要拷貝到幸存區(qū),如果幸存區(qū)比較小,拷貝的對(duì)象比較大,對(duì)象可能會(huì)直接拷貝到老年代,這樣會(huì)增加老年代GC的頻率(老年代GC我可以簡單理解為大GC或fullgc,這個(gè)過程非常慢)性能會(huì)受到影響。而分代的回收思想就會(huì)被弱化.
3.9:JVM年輕帶伊甸園區(qū)設(shè)置的比例比較小會(huì)有什么問題?
????我們程序中新創(chuàng)建的對(duì)象,大部分要存儲(chǔ)到伊甸園區(qū)假如伊甸園設(shè)置的比較小,會(huì)增加GC的頻率,可能會(huì)導(dǎo)致STW的時(shí)間變長。影響系統(tǒng)性能
3.10:JVM堆內(nèi)存為什么要分成年輕帶和老年代?
????為了更好的實(shí)現(xiàn)垃圾回收,減少GC時(shí)長,提高其執(zhí)行效率(可以思考這樣的一個(gè)問題,是掃描整個(gè)堆內(nèi)存的時(shí)間短,還是掃描局部空間的時(shí)間短)
3.11:項(xiàng)目中最大堆和初始堆的大小為什么推薦設(shè)置為一樣的?
????避免程序運(yùn)行過程中,因?qū)ο蠖嗌倩騁C后內(nèi)存發(fā)生了編號(hào)而調(diào)整堆大小,帶來的更大的系統(tǒng)開銷,在很多大廠的開發(fā)規(guī)范中都推薦初始堆和最大堆的大小是一樣的。(例如阿里的開發(fā)手冊(cè))
3.12:什么情況下對(duì)象會(huì)存儲(chǔ)到老年代
????創(chuàng)建的對(duì)象比較大,年輕代沒有空間存儲(chǔ)
????經(jīng)過多次GC,沒有被回收的對(duì)象年齡在增加,默認(rèn)15歲后會(huì)移動(dòng)到老年代
3.13:Java中所有的對(duì)象都是在堆上分配內(nèi)存的?
????隨著技術(shù)的升級(jí),這個(gè)說法已經(jīng)不準(zhǔn)確了,小對(duì)象未逃逸還可以直接分配在棧上
3.14:如何理解JVM方法區(qū)以及它的構(gòu)成是怎樣的?
????方法區(qū)(Method Area)是JVM中的一種邏輯上的規(guī)范,不同JDK對(duì)規(guī)范的落地會(huì)有不同,
例如在HDK8的HotSport虛擬機(jī)中稱之為Metaspace,方法區(qū)主要用于存儲(chǔ)與被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯后的代碼等數(shù)據(jù),
3.15:JDK8中Hotsport虛擬機(jī)的方法區(qū)在哪里
????JVM堆外內(nèi)存,嚴(yán)格來講屬于操作系統(tǒng)的一部分內(nèi)存,也可以通過參數(shù)設(shè)置具體大小,假如沒有設(shè)置,可以無限增大,直到操作系統(tǒng)內(nèi)存不足。
3.16:什么是逃逸分析以及可以解決什么問題
????逃逸分析是一種數(shù)據(jù)分析算法,基于此算法可以檢測(cè)對(duì)象是否發(fā)生了逃逸,為逃逸的小對(duì)象可以分配在棧上,也可以進(jìn)行標(biāo)量替換,還可以實(shí)現(xiàn)鎖消除,總之,可以有效減少Java對(duì)象在堆內(nèi)存中的分配,可以減少線程阻塞,提高其執(zhí)行效率。
3.17:如何理解對(duì)象的標(biāo)量替換,為什么要進(jìn)行標(biāo)量替換?
????將為逃逸的小對(duì)象直接打散分配到棧上,減少堆中的創(chuàng)建次數(shù),堆中對(duì)象創(chuàng)建的少了,GC的頻率就會(huì)降低,GC頻率降低了,系統(tǒng)正常業(yè)務(wù)的執(zhí)行效率就會(huì)提高。
3.18:逃逸分析有什么缺陷
????逃逸分析這種算法還不夠成熟,還在不斷調(diào)整,假如需要進(jìn)行逃逸分析的代碼比較多,可能會(huì)對(duì)性能有影響
3.19:什么是內(nèi)存溢出以及導(dǎo)致內(nèi)存溢出的的原因
????內(nèi)存中剩余的內(nèi)存不足以分配給新的內(nèi)存請(qǐng)求,此時(shí)就會(huì)出現(xiàn)內(nèi)存溢出(OutOfMemoryError)內(nèi)存溢出可能直接導(dǎo)致系統(tǒng)崩潰,導(dǎo)致內(nèi)存溢出的原因可能會(huì)有一下幾種:
????1創(chuàng)建的對(duì)象太大導(dǎo)致堆內(nèi)存溢出(內(nèi)存中沒有連續(xù)的內(nèi)存空間可以存儲(chǔ)你這個(gè)大對(duì)象)
????2創(chuàng)建的對(duì)象太多導(dǎo)致堆內(nèi)存溢出(對(duì)象創(chuàng)建的太多,又不能及時(shí)回收這些對(duì)象)
????3方法出現(xiàn)了無線遞歸調(diào)用導(dǎo)致棧內(nèi)存溢出(每次方法的調(diào)用都會(huì)對(duì)應(yīng)這個(gè)一個(gè)棧幀對(duì)象的創(chuàng)建,同時(shí)將棧幀入棧)
????4方法區(qū)的內(nèi)存空間不足導(dǎo)致內(nèi)存溢出(將如內(nèi)存不斷的加載新的類,類越來越多,此時(shí)可能出現(xiàn)內(nèi)存溢出)
????5出現(xiàn)大量的內(nèi)存泄露
3.20什么是內(nèi)存泄露以及內(nèi)存泄露的原因
????程序運(yùn)行時(shí),動(dòng)態(tài)分配的內(nèi)存空間,在使用完畢后未得到釋放,結(jié)構(gòu)導(dǎo)致一直占用著內(nèi)存單一,直到程序運(yùn)行結(jié)束,這個(gè)現(xiàn)象稱之為內(nèi)存泄露,導(dǎo)致內(nèi)存泄露的原因可能有如下幾點(diǎn):
????1大量使用靜態(tài)變量(靜態(tài)變量與程序生命周期一樣)
????2IO/連接資源用完沒有關(guān)閉(記得執(zhí)行close操作)
????3內(nèi)部類的使用方式存在問題(實(shí)例內(nèi)部類默認(rèn)引用外部類對(duì)象)
????4緩存(Cache)應(yīng)用不當(dāng)(盡量不要使用強(qiáng)引用)
????5ThreadLocal應(yīng)用不當(dāng)(用完記得執(zhí)行remove操作)
3.21:Java中四大引用類型有什么特點(diǎn)
????Java中為了更好的控制對(duì)象的生命周期,提高對(duì)象堆內(nèi)存的敏感度,設(shè)計(jì)了四種類型的引用。按其在內(nèi)存中的生命力強(qiáng)弱,可分為強(qiáng)引用、軟引用、弱引用、虛引用,其中“強(qiáng)引用”引用的對(duì)象生命力最強(qiáng),其他引用引用的對(duì)象生命力依次遞減,JVM的GC系統(tǒng)被觸發(fā)時(shí),會(huì)因?qū)ο笠玫牟煌?,?zhí)行不同的對(duì)象回收邏輯.
3.22:項(xiàng)目中哪些地方用到了緩存
????數(shù)據(jù)庫內(nèi)置的緩存?(例如myqsl的查詢緩存)
????數(shù)據(jù)層緩存(一般持久層框架提供,如MyBatis)
????業(yè)務(wù)層緩存(基于map等實(shí)現(xiàn)的本地緩存,分布式緩存--例如redis)
????cpu緩存(高速緩存區(qū))
????瀏覽器內(nèi)置緩存
3.23:假如讓你設(shè)計(jì)一個(gè)緩存你會(huì)考慮那些問題
????存儲(chǔ)結(jié)構(gòu)(使用什么結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)效率會(huì)更高?--散列表)
????淘汰算法(緩存容量有限--LRU/FIFO/LFU)
????任務(wù)調(diào)度(定期刷新緩存,緩存失效時(shí)間)
????并發(fā)安全(緩存并發(fā)訪問時(shí)的線程安全)
????日志記錄(緩存是否命中,命中率是多少)
????序列化(存對(duì)象時(shí)序列化,取對(duì)象時(shí)反序列化)
4字節(jié)碼增強(qiáng)部分
4.1:什么是字節(jié)碼
????符合JVM虛擬機(jī)規(guī)范的操作指令,可以被JVM執(zhí)行引擎解釋執(zhí)行。
4.2:為何要學(xué)習(xí)字節(jié)碼
????更好的理解JAVA代碼,例如對(duì)于Integer a = 100如何封裝為Integer類型的,你如何知道注解本質(zhì)上也是一個(gè)接口,你怎么知道你寫的枚舉類型默認(rèn)都繼承Enum,你如何知道synchronized是如何進(jìn)行加鎖的等。當(dāng)我們掌握了字節(jié)碼的基本特征后,就可以直接基于字節(jié)碼對(duì)類功能進(jìn)行增強(qiáng),同時(shí)還可以創(chuàng)作出類似java語言的編程語言,(例如Scala語言),只要這個(gè)語言編譯完成后生成的字節(jié)碼符合JVM規(guī)范即可。
4.3:如何解讀字節(jié)碼內(nèi)容?
? ? 我們解讀字節(jié)碼是通常會(huì)借助如下幾種方式:
????方式一:借助Hex-Editor插件可以查看字節(jié)碼的16進(jìn)制形式(notepad++)
????方式二:借助javap指令直接查看字節(jié)碼指令(javap -v -p 類名.class)
????方式三:借助jclasslib插件查看字節(jié)碼指令(可以在idea中安裝這個(gè)插件,然后基于此插件查看字節(jié)碼)
4.4:字節(jié)碼內(nèi)容由那幾部分構(gòu)成
????魔術(shù)+版本號(hào)+常量池+類訪問標(biāo)識(shí)+父類引用+接口數(shù)+成員變量信息+方法信息+其他屬性
4.5:什么是字節(jié)碼增強(qiáng)?
????在類加載或類運(yùn)行時(shí)對(duì)類的字節(jié)碼進(jìn)行修改或生成新的字節(jié)碼,這個(gè)過程我們稱之為字節(jié)碼增強(qiáng)
4.6:為什么要進(jìn)行字節(jié)碼增強(qiáng)
????通過這種方式可以實(shí)現(xiàn)類功能的增強(qiáng),同時(shí)還可以簡化部分重復(fù)代碼的編寫,例如AOP的設(shè)計(jì)、熱部署等技術(shù)實(shí)現(xiàn)都用到了字節(jié)碼增強(qiáng)技術(shù).
4.7:你了解那些字節(jié)碼增強(qiáng)技術(shù)?
????ASM技術(shù)(更加遍地層-CGLB代理底層就是是用來ASM技術(shù)做字節(jié)碼增強(qiáng)),Javassist技術(shù)等,我們可以基于這些技術(shù)修改類的字節(jié)碼,創(chuàng)建新的接口、類、添加屬性、方法都可以
ASM技術(shù)和興API如圖

Javassist技術(shù)的核心API如圖所示:

4.8:什么是熱替換以及如何實(shí)現(xiàn)?
????熱替換我們可以理解為一種在線升級(jí)技術(shù),就是在服務(wù)運(yùn)行過程中,不重啟服務(wù)就可以完成系統(tǒng)的在線升級(jí)。目前在java中實(shí)現(xiàn)熱替換(熱部署),可以寄基于Java Agent技術(shù),此技術(shù)可以侵入正在運(yùn)行的JVM應(yīng)用程序,并借助ASM或Javassist技術(shù)修改或擴(kuò)展目標(biāo)類型,并通過新的目標(biāo)類型字節(jié)碼替換正在運(yùn)行的JVM字節(jié)碼,以達(dá)到熱部署的目的

5JVM垃圾回收部分
5.1:GC?
????GC(Garbage Collection)稱之為垃圾回收,是對(duì)內(nèi)存中的垃圾對(duì)象采用一定的算法進(jìn)行內(nèi)存回收一個(gè)動(dòng)作。JAVA中支持自動(dòng)GC
5.2:為什么要GC?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?????深入理解GC工作機(jī)制,可以幫您更好的寫出Java應(yīng)用(避免入內(nèi)存泄露,提高運(yùn)行效率),提高開發(fā)效率?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
5.3:如何判定對(duì)象是否為垃圾
????JVM判斷對(duì)象是否為垃圾對(duì)象,通常有兩種策略:
????1.引用計(jì)數(shù)法(每個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器,這個(gè)計(jì)數(shù)器用于記錄有多少個(gè)引用引用著這個(gè)對(duì)象,假如計(jì)數(shù)器的值為0,說明沒有任何引用引用此對(duì)象,這個(gè)對(duì)象就是垃圾對(duì)象,但是引用計(jì)數(shù)器有一個(gè)弊端,可能會(huì)出現(xiàn)循環(huán)引用,對(duì)象無法回收,可以考慮弱引用、軟引用)
????2.可達(dá)性分析法(從GC root引用查找對(duì)象,假如訪問不到則認(rèn)為對(duì)象不可達(dá),不可達(dá)對(duì)象則認(rèn)為是垃圾對(duì)象)
5.4:你知道那些GC算法?
????標(biāo)記清除:標(biāo)記清除會(huì)首先掃描內(nèi)存,對(duì)內(nèi)存中活著的對(duì)象進(jìn)行標(biāo)記,任何再次掃描內(nèi)存對(duì)未標(biāo)記的內(nèi)存進(jìn)行清除,這個(gè)算法會(huì)掃描兩次內(nèi)存,效率可能會(huì)比較低,同時(shí)還可能產(chǎn)生大量的碎片,這種算法可以應(yīng)用于JVM中的老年代
????標(biāo)記復(fù)制:標(biāo)記復(fù)制這個(gè)算法會(huì)先掃描內(nèi)存,對(duì)活著的對(duì)象進(jìn)行標(biāo)記,然后將其拷貝到一塊空閑的內(nèi)存中,原先的內(nèi)存進(jìn)行釋放,同時(shí)也不會(huì)產(chǎn)生大量的內(nèi)存碎片,這種算法會(huì)犧牲一定的內(nèi)存空間,適合活著的對(duì)象比較少的內(nèi)存區(qū),年輕代,
????標(biāo)記整理:首先會(huì)掃描內(nèi)存找到活著的對(duì)象,然后將這些對(duì)象向一側(cè)移動(dòng),最后將邊界之外的內(nèi)存進(jìn)行清空即可。
5.5:JVM中有哪些垃圾回收器。
????串行(Serial)、并行(Parallel)、并發(fā)(CMS)、G1(收集器-邏輯上分代-但是粒度會(huì)更小) 等
5.6:如何查看JVM默認(rèn)的垃圾回收器?
????-XX:+PrintCommandLineFlags -version
5.7說幾個(gè)常用的JVM配置參數(shù)?
1)堆棧配置相關(guān)
-Xmx3550m: 最大堆大小為3550m。
-Xms3550m: 設(shè)置初始堆大小為3550m。
-Xmn2g: 設(shè)置年輕代大小為2g。
-Xss128k: 每個(gè)線程的堆棧大小為128k。
-XX:NewRatio=4: 設(shè)置年輕代(包括Eden和兩個(gè)Survivor區(qū))與年老代的比值(除去持久 代)。
-XX:SurvivorRatio=4: 設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。設(shè)置為4,則兩個(gè) Survivor區(qū)與一個(gè)Eden區(qū)的比值為2:4,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/6
-XX:MaxTenuringThreshold=0: 設(shè)置垃圾最大年齡。如果設(shè)置為0的話,則年輕代對(duì)象不 經(jīng)過Survivor區(qū),直接進(jìn)入年老代。
2)垃圾收集器相關(guān)?
-XX:+UseParallelGC: 選擇垃圾收集器為并行收集器。
-XX:ParallelGCThreads=20: 配置并行收集器的線程數(shù)
-XX:+UseConcMarkSweepGC: 設(shè)置年老代為并發(fā)收集。
-XX:CMSFullGCsBeforeCompaction:由于并發(fā)收集器不對(duì)內(nèi)存空間進(jìn)行壓縮、整理,所以運(yùn)行一段時(shí)間以后會(huì)產(chǎn)生“碎片”,使得運(yùn)行效率降低。此值設(shè)置運(yùn)行多少次GC以后對(duì)內(nèi)存 空間進(jìn)行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection: 打開對(duì)年老代的壓縮。可能會(huì)影響性能,但是可以消除碎片
3)輔助信息相關(guān) -XX:+PrintGC 輸出形式
5.8:JAVA中堆區(qū)為什么要分代?
????因?yàn)镚C過程都觸發(fā)STW(stop the world),也就是說可能要暫停正常業(yè)務(wù)的執(zhí)行,影響執(zhí)行效率,如果能夠想辦法縮短一次GC的時(shí)長,那我們是否可以只手機(jī)其中的一部分內(nèi)存區(qū)域,基于這樣的一種原因就產(chǎn)生分代設(shè)計(jì)思想
5.9:服務(wù)頻繁fullgc,younggc次數(shù)較少,可能原因?
????1.經(jīng)常有超過大對(duì)象閾值的對(duì)象進(jìn)入到老年代,可以通過-XX:PretenureSizeThreshold 設(shè)置,大于這個(gè)值的參數(shù)直接分配在老年代上
????2.老年代參數(shù)設(shè)置不當(dāng),-XX:CMSInitiatingOccupancyFaction=92設(shè)置不合理(閾值達(dá)到多少才進(jìn)行一次CMS垃圾回收),導(dǎo)致頻繁FULLGC;
????3.fullgc之后沒有整理老年代內(nèi)存碎片,導(dǎo)致沒有連接可用的內(nèi)存地址,進(jìn)入惡性循環(huán),導(dǎo)致頻繁fullgc,-XX:CMSFullGCsBeforeCompaction可以設(shè)置
????4.新生代過小,活著E區(qū)和S區(qū)比例不當(dāng),對(duì)象通過動(dòng)態(tài)年齡判斷機(jī)制頻繁進(jìn)入老年代。
????5.不合理使用System.gc(),造成頻繁的FullGC,-XX:+DisableExplicitGC這個(gè)參數(shù)可以禁用System.gc().
????6.存在內(nèi)存泄露,老年代中駐扎著大量不可回收的對(duì)象,一定程度上縮小了老年代的大小,
造成對(duì)象一進(jìn)入老年代就觸發(fā)FULLGC
????7.Meatspace不夠用引發(fā)fullgc,甚至無限fullgc,這類問題常見于tomcat熱部署,以及使用反射不當(dāng)。
5.10:你知道哪些 JVM 小工具?
????Jps
jps 主要用來輸出 JVM 中運(yùn)行的進(jìn)程狀態(tài)信息。語法格式如下:
jps [options] [hostid]
-q 不輸出類名、Jar名和傳入main方法的參數(shù)
-m 輸出傳入main方法的參數(shù)
-l 輸出main類或Jar的全限名
-v 輸出傳入JVM的參數(shù)
?Jstack
jstack 主要用來查看某個(gè) Java 進(jìn)程內(nèi)的線程堆棧信息。語法格式如下:
jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip
?jmap
jmap導(dǎo)出堆內(nèi)存,然后使用jhat來進(jìn)行分析,jmap語法格式如下:
jmap [option] pid jmap [option] executable core jmap [option] [server-id@]remote-hostname-or-ip
使用 jmap -heap pid 查看進(jìn)程堆內(nèi)存使用情況
?jstat
jstat是JVM統(tǒng)計(jì)監(jiān)測(cè)工具,看看各個(gè)區(qū)內(nèi)存和GC的情況。
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
例如:
jstat -gc 21711 250 4
vmid 是 Java 虛擬機(jī) ID,在 Linux/Unix 系統(tǒng)上一般就是進(jìn)程 ID。interval 是采樣時(shí)間間隔。count 是采樣數(shù)目。比如下面輸出的是 GC 信息,采樣時(shí)間間隔為 250ms,采樣數(shù)為 4?