虛擬機(jī)篇
1. JVM 內(nèi)存結(jié)構(gòu)
要求
- 掌握 JVM 內(nèi)存結(jié)構(gòu)劃分
- 尤其要知道方法區(qū)、永久代、元空間的關(guān)系
結(jié)合一段 java 代碼的執(zhí)行理解內(nèi)存劃分
- 執(zhí)行 javac 命令編譯源代碼為字節(jié)碼
- 執(zhí)行 java 命令
- 創(chuàng)建 JVM,調(diào)用類加載子系統(tǒng)加載 class,將類的信息存入方法區(qū)
- 創(chuàng)建 main 線程,使用的內(nèi)存區(qū)域是 JVM 虛擬機(jī)棧,開始執(zhí)行 main 方法代碼
- 如果遇到了未見過(guò)的類,會(huì)繼續(xù)觸發(fā)類加載過(guò)程,同樣會(huì)存入方法區(qū)
- 需要?jiǎng)?chuàng)建對(duì)象,會(huì)使用堆內(nèi)存來(lái)存儲(chǔ)對(duì)象
- 不再使用的對(duì)象,會(huì)由垃圾回收器在內(nèi)存不足時(shí)回收其內(nèi)存
- 調(diào)用方法時(shí),方法內(nèi)的局部變量、方法參數(shù)所使用的是 JVM 虛擬機(jī)棧中的棧幀內(nèi)存
- 調(diào)用方法時(shí),先要到方法區(qū)獲得到該方法的字節(jié)碼指令,由解釋器將字節(jié)碼指令解釋為機(jī)器碼執(zhí)行
- 調(diào)用方法時(shí),會(huì)將要執(zhí)行的指令行號(hào)讀到程序計(jì)數(shù)器,這樣當(dāng)發(fā)生了線程切換,恢復(fù)時(shí)就可以從中斷的位置繼續(xù)
- 對(duì)于非 java 實(shí)現(xiàn)的方法調(diào)用,使用內(nèi)存稱為本地方法棧(見說(shuō)明)
- 對(duì)于熱點(diǎn)方法調(diào)用,或者頻繁的循環(huán)代碼,由 JIT 即時(shí)編譯器將這些代碼編譯成機(jī)器碼緩存,提高執(zhí)行性能 (否則每次執(zhí)行相同的代碼,都要解釋器重復(fù)地將字節(jié)碼指令解釋為機(jī)器碼執(zhí)行,相當(dāng)于對(duì)字節(jié)碼指令做了緩存)
方法區(qū):存放類的相關(guān)信息(類的名稱、繼承關(guān)系、引用的其他類的符號(hào)、成員變量、方法的字節(jié)碼、類和方法和成員變量上加的注解等等)
堆: 存放new出來(lái)的對(duì)象
JVM 虛擬機(jī)棧:存放方法內(nèi)的局部變量和方法參數(shù) java實(shí)現(xiàn)的普通方法變量都存在這里,以前需要和os交互的特殊方法需要到本地方法棧去執(zhí)行,但是現(xiàn)在Oracle公司的 Hotspot 虛擬機(jī)實(shí)現(xiàn)已經(jīng)不再使用本地方法棧,或者說(shuō)兩個(gè)棧合二為一了,所有方法需要的變量?jī)?nèi)存都在JVM 虛擬機(jī)棧中
說(shuō)明
- 加粗字體代表了 JVM 虛擬機(jī)組件
- 對(duì)于 Oracle 的 Hotspot 虛擬機(jī)實(shí)現(xiàn),不區(qū)分虛擬機(jī)棧和本地方法棧
會(huì)發(fā)生內(nèi)存溢出的區(qū)域
內(nèi)存溢出: 該區(qū)域內(nèi)存耗盡了,報(bào)錯(cuò)了
內(nèi)存泄漏:垃圾回收器無(wú)法回收某部分內(nèi)存,這種現(xiàn)象就叫做內(nèi)存泄漏;
上圖中5塊內(nèi)存區(qū)域,除了程序計(jì)數(shù)器,都會(huì)產(chǎn)生內(nèi)存溢出
- 不會(huì)出現(xiàn)內(nèi)存溢出的區(qū)域 – 程序計(jì)數(shù)器
- 出現(xiàn) OutOfMemoryError 的情況
- 堆內(nèi)存耗盡 – 對(duì)象越來(lái)越多,又一直在使用,不能被垃圾回收
- 方法區(qū)內(nèi)存耗盡 – 加載的類越來(lái)越多,很多框架都會(huì)在運(yùn)行期間動(dòng)態(tài)產(chǎn)生新的類
- 虛擬機(jī)棧累積 – 每個(gè)線程最多會(huì)占用 1 M 內(nèi)存,線程個(gè)數(shù)越來(lái)越多,而又長(zhǎng)時(shí)間運(yùn)行不銷毀時(shí)
- 出現(xiàn) StackOverflowError 的區(qū)域
- JVM 虛擬機(jī)棧,原因有方法遞歸調(diào)用未正確結(jié)束、反序列化 json 時(shí)循環(huán)引用 (線程內(nèi)方法不斷調(diào)用,而每個(gè)線程內(nèi)的1M內(nèi)存消耗掉,就會(huì)報(bào)StackOverflowError)
方法區(qū)、永久代、元空間
- 方法區(qū)是 JVM 規(guī)范中定義的一塊內(nèi)存區(qū)域,用來(lái)存儲(chǔ)類元數(shù)據(jù)、方法字節(jié)碼、即時(shí)編譯器需要的信息等
- 永久代是 Hotspot 虛擬機(jī)對(duì) JVM 規(guī)范的實(shí)現(xiàn)(1.8 之前)
- 元空間是 Hotspot 虛擬機(jī)對(duì) JVM 規(guī)范的另一種實(shí)現(xiàn)(1.8 以后),使用本地內(nèi)存作為這些信息的存儲(chǔ)空間
方法區(qū)只是 JVM 規(guī)范中的一種定義 (你得有,怎么實(shí)現(xiàn)我不管)
永久代和元空間才是對(duì)規(guī)范的物理實(shí)現(xiàn)
從這張圖學(xué)到三點(diǎn)
類元數(shù)據(jù): 描述類的數(shù)據(jù) (哪些成員,什么類型,長(zhǎng)度多少…) 存儲(chǔ)在元空間(方法區(qū)的物理實(shí)現(xiàn))
類名.class 字節(jié)碼對(duì)象,既然是對(duì)象,自然就存儲(chǔ)在堆中了
類的原始信息(類元數(shù)據(jù))存儲(chǔ)在元空間中,無(wú)法直接訪問(wèn),得通過(guò)java對(duì)象訪問(wèn),這個(gè)對(duì)象就是字節(jié)碼對(duì)象
- 當(dāng)?shù)谝淮斡玫侥硞€(gè)類時(shí),由類加載器將 class 文件的類元信息讀入,并存儲(chǔ)于元空間
- X,Y 的類元信息是存儲(chǔ)于元空間中,無(wú)法直接訪問(wèn)
- 可以用 X.class,Y.class 間接訪問(wèn)類元信息,它們倆屬于 java 對(duì)象 (字節(jié)碼對(duì)象),我們的代碼中可以使用
從這張圖可以學(xué)到
- 堆內(nèi)存中:當(dāng)一個(gè)類加載器對(duì)象,這個(gè)類加載器對(duì)象加載的所有類對(duì)象,這些類對(duì)象對(duì)應(yīng)的所有實(shí)例對(duì)象都沒人引用時(shí),GC 時(shí)就會(huì)對(duì)它們占用的堆內(nèi)存進(jìn)行釋放
- 元空間中:內(nèi)存釋放以類加載器為單位,當(dāng)堆中類加載器內(nèi)存釋放時(shí),對(duì)應(yīng)的元空間中的類元信息也會(huì)釋放
一般系統(tǒng)類加載器不會(huì)被釋放,我們自定義的類加載器不再使用時(shí)會(huì)被釋放( 釋放啥? 元空間內(nèi)存啊 )
2. JVM 內(nèi)存參數(shù)
要求
- 熟悉常見的 JVM 參數(shù),尤其和大小相關(guān)的
提問(wèn):
堆內(nèi)存,按大小設(shè)置
解釋:
- -Xms JVM最小內(nèi)存(包括新生代和老年代)
- -Xmx JVM最大內(nèi)存(包括新生代和老年代)
- 通常建議將 -Xms 與 -Xmx 設(shè)置為大小相等,即不需要保留內(nèi)存,不需要從小到大增長(zhǎng),這樣性能較好
- -XX:NewSize 與 -XX:MaxNewSize 設(shè)置新生代的最小與最大值,但一般不建議設(shè)置,由 JVM 自己控制
- -Xmn 設(shè)置新生代大小,相當(dāng)于同時(shí)設(shè)置了 -XX:NewSize 與 -XX:MaxNewSize 并且取值相等
- 保留是指,一開始不會(huì)占用那么多內(nèi)存,隨著使用內(nèi)存越來(lái)越多,會(huì)逐步使用這部分保留內(nèi)存。下同
從年代角度,JVM將內(nèi)存劃分為新生代和老年代
-Xmn的n就是new 新生代
堆內(nèi)存,按比例設(shè)置
下圖的 new 就是新生代,新生代內(nèi)存可以進(jìn)一步劃分為eden和Survivor,Survivor又可以細(xì)分為:from,to
old 自然就是老年代內(nèi)存
解釋:
- -XX:NewRatio=2:1 表示老年代占兩份,新生代占一份
- -XX:SurvivorRatio=4:1 表示新生代分成六份,伊甸園占四份,from 和 to 各占一份
- (注意1:默認(rèn)8:1 也就是8:1:1) (注意2:上面的4:1指的是eden:from=eden:to=4:1 因?yàn)閒rom和to總是相等的)
元空間內(nèi)存設(shè)置
解釋:
- class space 存儲(chǔ)類的基本信息,最大值受 -XX:CompressedClassSpaceSize 控制
- non-class space 存儲(chǔ)除類的基本信息以外的其它信息(如方法字節(jié)碼、注解等)
- class space 和 non-class space 總大小受 -XX:MaxMetaspaceSize 控制
注意:
- 這里 -XX:CompressedClassSpaceSize 這段空間還與是否開啟了指針壓縮有關(guān),這里暫不深入展開,可以簡(jiǎn)單認(rèn)為指針壓縮默認(rèn)開啟
代碼緩存內(nèi)存設(shè)置
JIT即時(shí)編譯器,將熱點(diǎn)代碼編譯成機(jī)器碼后緩存起來(lái),就存放在CodeCache 代碼緩存區(qū)
解釋:
- 如果 -XX:ReservedCodeCacheSize < 240m,所有優(yōu)化機(jī)器代碼不加區(qū)分存在一起
- 否則,分成三個(gè)區(qū)域(優(yōu)化代碼細(xì)分3份)(圖中筆誤 mthod 拼寫錯(cuò)誤,少一個(gè) e)
- non-nmethods - JVM 自己用的代碼 (JIT編譯器自己的代碼)
- profiled nmethods - 部分優(yōu)化的機(jī)器碼
- non-profiled nmethods - 完全優(yōu)化的機(jī)器碼
線程內(nèi)存設(shè)置
也就是JVM虛擬機(jī)棧的內(nèi)存
-Xss 設(shè)置每個(gè)線程占用的內(nèi)存
不設(shè)置,linux系統(tǒng)默認(rèn)1MB, 也就是每個(gè)線程默認(rèn)占用1MB內(nèi)存
官方參考文檔
- https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
3. JVM 垃圾回收
要求
- 掌握垃圾回收算法
- 掌握分代回收思想
- 理解三色標(biāo)記及漏標(biāo)處理
- 了解常見垃圾回收器
eg: 堆內(nèi)存中一些對(duì)象,已經(jīng)沒有任何棧內(nèi)存中引用指向它,GC就可以將它回收了
三種垃圾回收算法
標(biāo)記清除法
解釋:
- 找到 GC Root 對(duì)象,即那些一定不會(huì)被回收的對(duì)象,如正執(zhí)行方法內(nèi)局部變量引用的對(duì)象、靜態(tài)變量引用的對(duì)象
- 標(biāo)記階段:沿著 GC Root 對(duì)象的引用鏈找,直接或間接引用到的對(duì)象加上標(biāo)記
- 清除階段:釋放未加標(biāo)記的對(duì)象占用的內(nèi)存
局部變量正在引用或者說(shuō)使用的對(duì)象一定不能回收,可以作為根對(duì)象
靜態(tài)變量得一直存在,不能被回收,可以作為根對(duì)象
要點(diǎn):
- 標(biāo)記速度與存活對(duì)象線性關(guān)系
- 清除速度與內(nèi)存大小線性關(guān)系
- 缺點(diǎn)是會(huì)產(chǎn)生內(nèi)存碎片 (未標(biāo)記的內(nèi)存極大概率都是不連續(xù)的,會(huì)產(chǎn)生大量?jī)?nèi)存碎片 所以基本上已經(jīng)被棄用了)
標(biāo)記整理法
解釋:
- 前面的標(biāo)記階段、清理階段與標(biāo)記清除法類似
- 多了一步整理的動(dòng)作,將存活對(duì)象向一端移動(dòng),可以避免內(nèi)存碎片產(chǎn)生
特點(diǎn):
-
標(biāo)記速度與存活對(duì)象線性關(guān)系
-
清除與整理速度與內(nèi)存大小成線性關(guān)系
-
缺點(diǎn)是性能上較慢
標(biāo)記復(fù)制法
解釋:
- 將整個(gè)內(nèi)存分成兩個(gè)大小相等的區(qū)域,from 和 to,其中 to 總是處于空閑,from 存儲(chǔ)新創(chuàng)建的對(duì)象
- 標(biāo)記階段與前面的算法類似
- 在找出存活對(duì)象后,會(huì)將它們從 from 復(fù)制到 to 區(qū)域,復(fù)制的過(guò)程中自然完成了碎片整理(復(fù)制完后from區(qū)全部都可以清除了)
- 復(fù)制完成后,交換 from 和 to 的位置即可 (兩個(gè)區(qū)域交替使用,永遠(yuǎn)不會(huì)產(chǎn)生內(nèi)存碎片問(wèn)題,多好啊)
特點(diǎn):
- 標(biāo)記與復(fù)制速度與存活對(duì)象成線性關(guān)系
- 缺點(diǎn)是會(huì)占用成倍的空間
GC 與分代回收算法
GC 的目的在于實(shí)現(xiàn)無(wú)用對(duì)象內(nèi)存自動(dòng)釋放,減少內(nèi)存碎片、加快分配速度
GC 要點(diǎn):
- 回收區(qū)域是堆內(nèi)存,不包括虛擬機(jī)棧 (方法棧中內(nèi)存,在方法調(diào)用結(jié)束會(huì)自動(dòng)釋放方法占用內(nèi)存)
- 判斷無(wú)用對(duì)象,使用可達(dá)性分析算法,三色標(biāo)記法標(biāo)記存活對(duì)象,回收未標(biāo)記對(duì)象
- GC 具體的實(shí)現(xiàn)稱為垃圾回收器
- GC 大都采用了分代回收思想
- 理論依據(jù)是大部分對(duì)象朝生夕滅,用完立刻就可以回收,另有少部分對(duì)象會(huì)長(zhǎng)時(shí)間存活,每次很難回收
- 根據(jù)這兩類對(duì)象的特性將回收區(qū)域分為新生代和老年代,新生代采用標(biāo)記復(fù)制法、老年代一般采用標(biāo)記整理法
- 根據(jù) GC 的規(guī)??梢苑殖?Minor GC,Mixed GC,Full GC
新生代:垃圾對(duì)象比較多 (方法內(nèi)經(jīng)常new的局部對(duì)象)
老年代: 存活對(duì)象比較多,很難回收,或者說(shuō)不需要經(jīng)常回收,整理也不會(huì)特別耗時(shí) (eg: 靜態(tài)對(duì)象,框架里面長(zhǎng)期使用的對(duì)象) (老年代存活對(duì)象多,標(biāo)記復(fù)制法也會(huì)極其浪費(fèi)內(nèi)存)
可達(dá)性分析算法:找到GC Root 打上標(biāo)記 (先找到一定不會(huì)被回收的對(duì)象,然后沿著其引用鏈再找,再標(biāo)記)
三色標(biāo)記法:見下文
垃圾回收器有很多種,見下文
.
Minor GC:新生代的垃圾回收,小范圍垃圾回收,暫停時(shí)間短,對(duì)系統(tǒng)影響小
Full GC: 新生代和老年代都發(fā)生內(nèi)存不足了,來(lái)了一次全面的垃圾回收,暫停時(shí)間長(zhǎng),明顯感到系統(tǒng)卡頓,一般是不愿意看到Full GC的
Mixed GC: 位于以上二者之間,指的是:新生代發(fā)生了垃圾回收,部分的老年代也發(fā)生了垃圾回收,一種混合垃圾回收,G1垃圾回收器獨(dú)有的回收方式
個(gè)人再整理一下GC和堆內(nèi)存相關(guān)概念:
GC只是回收堆內(nèi)存
new出來(lái)的對(duì)象,都放在堆內(nèi)存
堆內(nèi)存劃分:
從年代角度,JVM將堆內(nèi)存劃分為新生代和老年代
新生代內(nèi)存又可以分為: eden和Survivor,Survivor又可以細(xì)分為:from,to
先總覽一下,有個(gè)大致框架: 再慢慢看下面詳細(xì)過(guò)程
圖中黃色是空閑,白色是已分配
打標(biāo)記可以用一句話概括: 尋找有沒有被根對(duì)象直接或者間接引用到的
分代回收
- 伊甸園 eden,最初對(duì)象都分配到這里,與幸存區(qū) survivor(分成 from 和 to)合稱新生代,
- 當(dāng)伊甸園內(nèi)存不足,標(biāo)記伊甸園與 from(現(xiàn)階段沒有)的存活對(duì)象
- 將存活對(duì)象采用復(fù)制算法復(fù)制到 to 中,復(fù)制完畢后,伊甸園和 from 內(nèi)存都得到釋放
- 將 from 和 to 交換位置
- 經(jīng)過(guò)一段時(shí)間后伊甸園的內(nèi)存又出現(xiàn)不足
- 標(biāo)記伊甸園與 from(現(xiàn)階段沒有)的存活對(duì)象
- 將存活對(duì)象采用復(fù)制算法復(fù)制到 to 中
- 復(fù)制完畢后,伊甸園和 from 內(nèi)存都得到釋放
- 將 from 和 to 交換位置
- 老年代 old,當(dāng)幸存區(qū)對(duì)象熬過(guò)幾次回收(最多15次),晉升到老年代(幸存區(qū)內(nèi)存不足或大對(duì)象會(huì)導(dǎo)致提前晉升)
幸存區(qū)不足: to不夠復(fù)制的,肯定把已經(jīng)在to的給移到老年代 (to很大的,不足肯定是有之前熬過(guò)了回收的對(duì)象存在的) 提前競(jìng)升也是沒有辦法的事情
大對(duì)象:每次GC都要復(fù)制來(lái)復(fù)制去的,太消耗了,不如提前競(jìng)升為老年代
GC 規(guī)模
-
Minor GC 發(fā)生在新生代的垃圾回收,暫停時(shí)間短
-
Mixed GC 新生代 + 老年代部分區(qū)域的垃圾回收,G1 收集器特有
-
Full GC 新生代 + 老年代 完整(全面) 垃圾回收,暫停時(shí)間長(zhǎng),應(yīng)盡力避免
三色標(biāo)記
即用三種顏色記錄對(duì)象的標(biāo)記狀態(tài)
- 黑色 – 已標(biāo)記
- 灰色 – 標(biāo)記中
- 白色 – 還未標(biāo)記
黑色 – 已標(biāo)記: 沿著根對(duì)象的引用鏈,已經(jīng)找到這個(gè)對(duì)象了,且此對(duì)象內(nèi)部的其他引用也已經(jīng)處理完成了
灰色 – 標(biāo)記中:沿著根對(duì)象的引用鏈,已經(jīng)找到這個(gè)對(duì)象了,但這個(gè)對(duì)象內(nèi)部的其他引用還沒有處理完
白色 – 還未標(biāo)記: 就是標(biāo)記完最終剩下的對(duì)象了
- 起始的三個(gè)對(duì)象還未處理完成,用灰色表示
- 該對(duì)象的引用已經(jīng)處理完成,用黑色表示,黑色引用的對(duì)象變?yōu)榛疑?/li>
將其直接引用標(biāo)記為灰色,就認(rèn)為他的引用處理完成了,就可以直接標(biāo)記為黑色了
- 依次類推
- 沿著引用鏈都標(biāo)記了一遍
- 最后未標(biāo)記的白色對(duì)象,即為垃圾
并發(fā)漏標(biāo)問(wèn)題
前面的GC是非并發(fā)的,GC在工作時(shí),用戶線程就暫停了,因此用戶線程不會(huì)對(duì)GC線程造成影響
也即GC在打標(biāo)記時(shí),用戶線程暫停了,不會(huì)對(duì)打標(biāo)記產(chǎn)生任何影響(不會(huì)修改引用鏈)
非并發(fā)GC效率低,并發(fā)GC,也即并發(fā)標(biāo)記,肯定是需要的
那么GC在打標(biāo)記時(shí),用戶線程還在工作,萬(wàn)一打標(biāo)過(guò)程中,用戶線程修改了引用關(guān)系,很容易導(dǎo)致漏標(biāo)啊
比較先進(jìn)的垃圾回收器都支持并發(fā)標(biāo)記,即在標(biāo)記過(guò)程中,用戶線程仍然能工作。但這樣帶來(lái)一個(gè)新的問(wèn)題,如果用戶線程修改了對(duì)象引用,那么就存在漏標(biāo)問(wèn)題。例如:
- 如圖所示標(biāo)記工作尚未完成
- 用戶線程同時(shí)在工作,斷開了第一層 3、4 兩個(gè)對(duì)象之間的引用,這時(shí)對(duì)于正在處理 3 號(hào)對(duì)象的垃圾回收線程來(lái)講,它會(huì)將 4 號(hào)對(duì)象當(dāng)做是白色垃圾
這個(gè)時(shí)候回收3其實(shí)也是合理的
但是萬(wàn)一他斷開后又被別的對(duì)象引用了呢(不是我們不用了,而是我給別人用了) 就不能回收了呀(見下)
- 但如果其他用戶線程又建立了 2、4 兩個(gè)對(duì)象的引用,這時(shí)因?yàn)?2 號(hào)對(duì)象是黑色已處理對(duì)象了,因此垃圾回收線程不會(huì)察覺到這個(gè)引用關(guān)系的變化,從而產(chǎn)生了漏標(biāo)
- 如果用戶線程讓黑色對(duì)象引用了一個(gè)新增對(duì)象,一樣會(huì)存在漏標(biāo)問(wèn)題
黑色對(duì)象已經(jīng)處理過(guò)了(被標(biāo)記為黑色的,會(huì)認(rèn)為已經(jīng)處理過(guò)了),已經(jīng)處理過(guò)的對(duì)象,不會(huì)再去處理他的(不會(huì)再重復(fù)地找他的直接引用然后標(biāo)記為灰色)
因此對(duì)于并發(fā)標(biāo)記而言,必須解決漏標(biāo)問(wèn)題,也就是要記錄標(biāo)記過(guò)程中的變化。有兩種解決方法:
解決漏標(biāo),核心就是:記錄標(biāo)記過(guò)程中的變化+二次處理
- Incremental Update 增量更新法,CMS 垃圾回收器采用
- 思路是攔截每次賦值動(dòng)作,只要賦值發(fā)生,被賦值的對(duì)象就會(huì)被記錄下來(lái),在重新標(biāo)記階段再確認(rèn)一遍
- Snapshot At The Beginning,SATB 原始快照法,G1 垃圾回收器采用
- 思路也是攔截每次賦值動(dòng)作,不過(guò)記錄的對(duì)象不同,也需要在重新標(biāo)記階段對(duì)這些對(duì)象二次處理
- 新加對(duì)象會(huì)被記錄
- 被刪除引用關(guān)系的對(duì)象也被記錄
上圖紅箭頭 黑->白 黑色對(duì)象就是被賦值對(duì)象(把白色對(duì)象賦值給黑色對(duì)象)
垃圾回收器 - Parallel GC
-
eden 內(nèi)存不足發(fā)生 Minor GC,采用標(biāo)記復(fù)制算法,需要暫停用戶線程
-
old 內(nèi)存不足發(fā)生 Full GC,采用標(biāo)記整理算法,需要暫停用戶線程
-
注重吞吐量 (響應(yīng)時(shí)間、暫停時(shí)間慢點(diǎn)沒關(guān)系,但是總體上暫停時(shí)間短一點(diǎn)就ok了)
Parallel GC: 實(shí)際上由2個(gè)垃圾回收器組成,一個(gè)工作在新生代,一個(gè)工作在老年代
Minor GC 僅僅新生代垃圾回收器工作
Full GC 時(shí),新生代和老年代垃圾回收器都會(huì)工作
標(biāo)記復(fù)制和標(biāo)記整理(慢)都不會(huì)有內(nèi)存碎片
垃圾回收器 - ConcurrentMarkSweep GC
-
它是工作在 old 老年代,支持并發(fā)標(biāo)記的一款回收器,采用并發(fā)清除算法
- 并發(fā)標(biāo)記時(shí)不需暫停用戶線程 (可能導(dǎo)致漏標(biāo))
- 重新標(biāo)記時(shí)仍需暫停用戶線程 (處理漏標(biāo)時(shí)用戶線程不能再并發(fā)了,得暫停,否則沒完沒了了)
-
如果并發(fā)失敗(即回收速度趕不上創(chuàng)建新對(duì)象速度),會(huì)觸發(fā) Full GC
-
注重響應(yīng)時(shí)間 (也就這一個(gè)好處 響應(yīng)時(shí)間很快 不需要等很久)
ConcurrentMarkSweep GC 這是一個(gè)老年代垃圾回收器,
ConcurrentMarkSweep GC 簡(jiǎn)稱 CMS垃圾回收器Concurrent:并發(fā)
Mark:標(biāo)記
Sweep: 掃描,打掃
并發(fā)就意味著GC時(shí)用戶線程暫停時(shí)間很短,可以并發(fā)執(zhí)行嘛
標(biāo)記指的是標(biāo)記為黑、灰、白三色,清除指的是清除回收白色垃圾對(duì)象
然而正因?yàn)槿思也捎玫氖?標(biāo)記清除法,有內(nèi)存碎片問(wèn)題,因此最新的JDK已經(jīng)將其標(biāo)記為廢棄了
STW(Stop The World)
垃圾回收器 - G1 GC
- 響應(yīng)時(shí)間與吞吐量兼顧
- 劃分成多個(gè)區(qū)域,每個(gè)區(qū)域都可以充當(dāng) eden,survivor,old, humongous,其中 humongous 專為大對(duì)象準(zhǔn)備
- 分成三個(gè)階段:新生代回收、并發(fā)標(biāo)記、混合收集
- 如果并發(fā)失?。椿厥账俣融s不上創(chuàng)建新對(duì)象速度),會(huì)觸發(fā) Full GC
G1 GC 讀作:G one 垃圾回收器
humongous: 巨大無(wú)比的
總覽:
G1也有保底策略:回收速度<新對(duì)象創(chuàng)建速度 也就是并發(fā)失敗 : FailBack Full GC 整體進(jìn)行一次回收,暫停時(shí)間會(huì)比較長(zhǎng)
G1 回收階段 - 新生代回收
- 初始時(shí),所有區(qū)域都處于空閑狀態(tài)
- 創(chuàng)建了一些對(duì)象,挑出一些空閑區(qū)域作為伊甸園區(qū)存儲(chǔ)這些對(duì)象
- 當(dāng)伊甸園需要垃圾回收時(shí),挑出一個(gè)空閑區(qū)域作為幸存區(qū),用復(fù)制算法復(fù)制存活對(duì)象,需要暫停用戶線程
(新生代采用標(biāo)記復(fù)制法,復(fù)制時(shí)要STW, 非并發(fā)的)
(eden區(qū)所有存活對(duì)象復(fù)制到一個(gè)幸存區(qū)(to區(qū) 然后to和from區(qū)互換地位 ))
- 復(fù)制完成,將之前的伊甸園內(nèi)存釋放
- 隨著時(shí)間流逝,伊甸園的內(nèi)存又有不足
- 將伊甸園以及之前幸存區(qū)中的存活對(duì)象,采用復(fù)制算法,復(fù)制到新的幸存區(qū),其中較老對(duì)象晉升至老年代
(eden區(qū)和幸存from區(qū)中的對(duì)象全部復(fù)制到新的幸存區(qū)(類似to))
- 釋放伊甸園以及之前幸存區(qū)的內(nèi)存
G1 回收階段 - 并發(fā)標(biāo)記與混合收集
前提,老年代內(nèi)存快不足了,才需要開始回收老年代,老年代標(biāo)記策略是:并發(fā)標(biāo)記
- 當(dāng)老年代占用內(nèi)存超過(guò)閾值后,觸發(fā)并發(fā)標(biāo)記,這時(shí)無(wú)需暫停用戶線程
也不是直接回收所有的老年代區(qū)域,而是挑選幾個(gè)回收價(jià)值高的老年代區(qū)域(存活對(duì)象很少)先進(jìn)行回收
- 并發(fā)標(biāo)記之后,會(huì)有重新標(biāo)記階段解決漏標(biāo)問(wèn)題,此時(shí)需要暫停用戶線程。這些都完成后就知道了老年代有哪些存活對(duì)象,隨后進(jìn)入混合收集階段。此時(shí)不會(huì)對(duì)所有老年代區(qū)域進(jìn)行回收,而是根據(jù)暫停時(shí)間目標(biāo)優(yōu)先回收價(jià)值高(存活對(duì)象少)的區(qū)域(這也是 Gabage First 名稱的由來(lái))。
混合收集,不僅收集挑選出來(lái)的回收價(jià)值高的老年代(上圖紅色),還收集新生代(eden+survivor)
- 混合收集階段中,參與復(fù)制的有 eden、survivor、old,下圖顯示了伊甸園和幸存區(qū)的存活對(duì)象復(fù)制
- 下圖顯示了老年代和幸存區(qū)晉升的存活對(duì)象的復(fù)制
- 復(fù)制完成,內(nèi)存得到釋放。進(jìn)入下一輪的新生代回收、并發(fā)標(biāo)記、混合收集
4. 內(nèi)存溢出
內(nèi)存溢出: 該區(qū)域內(nèi)存耗盡了,報(bào)錯(cuò)了
要求
- 能夠說(shuō)出幾種典型的導(dǎo)致內(nèi)存溢出的情況
典型情況
- 1)誤用線程池導(dǎo)致的內(nèi)存溢出
- 參考 day03.TestOomThreadPool
LinkedBlockingQueue就是一種無(wú)界隊(duì)列 (Interger類型不溢出,他就不會(huì)溢出)
- 參考 day03.TestOomThreadPool
上圖代碼不斷創(chuàng)建新的現(xiàn)場(chǎng)并提交,由于每個(gè)線程都要阻塞30ms,阻塞隊(duì)列越來(lái)越大,無(wú)限制增長(zhǎng),就會(huì)導(dǎo)致內(nèi)存爆
- 2)查詢數(shù)據(jù)量太大導(dǎo)致的內(nèi)存溢出
- 參考 day03.TestOomTooManyObject
數(shù)據(jù)庫(kù)條目太多了,你findAll, 一次查可能就100w條,就是100w個(gè)很普通的Product商品POJO集合,也要占用363MB的內(nèi)存, 服務(wù)器內(nèi)存再大,也經(jīng)不起這么造啊, 10個(gè)用戶就得占用3G內(nèi)存呀
所以后端開發(fā)千萬(wàn)不要findAll( 自己不要寫,也不要調(diào)用)
以后寫代碼,sql查詢一定要加limit (光有條件都不行,條件可能失效啊)
這些錯(cuò)誤在測(cè)試環(huán)境下是測(cè)不出來(lái)的,生產(chǎn)環(huán)境下才有百萬(wàn)級(jí)別的數(shù)據(jù),才會(huì)暴露出來(lái)的問(wèn)題
所以項(xiàng)目做完后,做一下壓力測(cè)試也是很有必要的,面試會(huì)問(wèn)到
- 3)動(dòng)態(tài)生成類導(dǎo)致的內(nèi)存溢出
- 參考 day03.TestOomTooManyClass
- 參考 day03.TestOomTooManyClass
5. 類加載
要求
- 掌握類加載階段
- 掌握類加載器
- 理解雙親委派機(jī)制
類加載過(guò)程的三個(gè)階段
-
加載
- 將類的字節(jié)碼載入方法區(qū),并創(chuàng)建類.class 對(duì)象
- 如果此類的父類沒有加載,先加載父類
- 加載是懶惰執(zhí)行 (真的用到此類時(shí)才加載)
類.class對(duì)象
里面有一系列反射方法,可以獲知類的所有信息:有哪些成員,有哪些方法類.class對(duì)象
存放在堆里面
-
鏈接
- 驗(yàn)證 – 驗(yàn)證類是否符合 Class 規(guī)范,合法性、安全性檢查
- 準(zhǔn)備 – 為 static 變量分配空間,設(shè)置默認(rèn)值 (但是手動(dòng)寫了賦值語(yǔ)句此時(shí)是不會(huì)執(zhí)行的,會(huì)在初始化階段執(zhí)行,這里其實(shí)只是給靜態(tài)變量分配空間 (final變量是例外,會(huì)在此時(shí)賦值))
- 解析 – 將常量池的符號(hào)引用解析為直接引用
-
初始化
- 靜態(tài)代碼塊、static 修飾的變量賦值、static final 修飾的引用類型變量賦值,會(huì)被合并成一個(gè)
<cinit>
方法,在初始化時(shí)被調(diào)用 - static final 修飾的基本類型變量賦值,在鏈接階段就已完成
- 初始化是懶惰執(zhí)行 (真正要用到該類時(shí)才會(huì)初始化 懶惰執(zhí)行,化整為零,多好)
- 靜態(tài)代碼塊、static 修飾的變量賦值、static final 修飾的引用類型變量賦值,會(huì)被合并成一個(gè)
驗(yàn)證手段
- 使用 jps 查看進(jìn)程號(hào)
- 使用 jhsdb 調(diào)試,執(zhí)行命令
jhsdb.exe hsdb
打開它的圖形界面
- Class Browser 可以查看當(dāng)前 jvm 中加載了哪些類
- 控制臺(tái)的 universe 命令查看堆內(nèi)存范圍
- 控制臺(tái)的 g1regiondetails 命令查看 region 詳情
scanoops 起始地址 結(jié)束地址 對(duì)象類型
可以根據(jù)類型查找某個(gè)區(qū)間內(nèi)的對(duì)象地址- 控制臺(tái)的
inspect 地址
指令能夠查看這個(gè)地址對(duì)應(yīng)的對(duì)象詳情- 使用 javap 命令可以查看 class 字節(jié)碼
代碼說(shuō)明文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-491341.html
- day03.loader.TestLazy - 驗(yàn)證類的加載是懶惰的,用到時(shí)才觸發(fā)類加載
- day03.loader.TestFinal - 驗(yàn)證使用 final 修飾的變量不會(huì)觸發(fā)類加載
字節(jié)碼對(duì)象確實(shí)在堆空間(eden區(qū)域),不在方法區(qū)
- 類初始化方法(靜態(tài)成員(非final普通類型)和靜態(tài)代碼塊)
會(huì)將靜態(tài)成員和靜態(tài)代碼快里的語(yǔ)句,整合在一起,變成一個(gè)方法(cinit方法),在類初始化時(shí)調(diào)用這個(gè)方法
注意:final static 非引用類型 的變量在類加載時(shí)(創(chuàng)建字節(jié)碼對(duì)象時(shí))就會(huì)初始化好的,這里不需要整合了
- 使用 final 修飾的非引用類型變量不會(huì)觸發(fā)類加載
前兩個(gè)打印語(yǔ)句,看起來(lái)使用了類,Student.c和Student.m 實(shí)際上并沒有真正使用到類,因此此時(shí)類并沒有被加載,內(nèi)存中并沒有類,充分證明了類的加載是懶加載
此時(shí)類加載完成了,可以看到類的字節(jié)碼信息了(類的結(jié)構(gòu):哪些成員、哪些方法)
當(dāng)一個(gè)類A使用另一個(gè)類B的final static 普通類型 變量,實(shí)際上是常量,這個(gè)時(shí)候類A直接將該類B常量復(fù)制一份到自己類中,根本不會(huì)真的用到另一個(gè)類B
如果常量數(shù)值比較小,那么直接就寫死在方法里
如果數(shù)值比較大,超過(guò)了short的最大范圍(>32767) 就會(huì)放到常量池子中,需要用到時(shí)到常量池中拿就好了
也即是: 數(shù)值較大,會(huì)復(fù)制到類A自己的常量池中,每個(gè)類都有自己的常量池(一個(gè)常量列表,且1,2,3,… 地給每個(gè)常量編好了號(hào),給出編號(hào),直接到常量池中取那個(gè)常量的值)
解析:符號(hào)引用-》直接引用 隨著代碼的執(zhí)行不斷進(jìn)行的過(guò)程,不是一次性就能完成的
類的static成員變量的引用,都是放在常量池的
沒有給靜態(tài)成員賦值時(shí),常量池中就沒有直接引用,只有符號(hào)引用(空指針 只知道要指向什么類型,但是并沒有真的內(nèi)存)
jdk 8 的類加載器
名稱 | 加載哪的類 | 說(shuō)明 |
---|---|---|
Bootstrap ClassLoader | JAVA_HOME/jre/lib | 無(wú)法直接訪問(wèn) |
Extension ClassLoader | JAVA_HOME/jre/lib/ext | 上級(jí)為 Bootstrap,顯示為 null |
Application ClassLoader | classpath | 上級(jí)為 Extension |
自定義類加載器 | 自定義 | 上級(jí)為 Application |
像String.class, Application和Extension類加載器中都沒有,無(wú)法加載,這個(gè)時(shí)候必須向上詢問(wèn)Bootstrap啟動(dòng)類加載器,讓他加載,然后下級(jí)都可見 (String類型是jdk的,上層都需要用到,所有都可見也是合理的)
像自己寫的類Student.class, 也會(huì)遵循規(guī)則先逐級(jí)向上詢問(wèn),上層加載器都沒有這個(gè)類,Application類加載器才有了加載Student.class的資格,進(jìn)行加載(上層類加載器不可見,也不需要可見,這種屏蔽很合理)
雙親委派機(jī)制
所謂的雙親委派,就是指優(yōu)先委派上級(jí)類加載器進(jìn)行加載,如果上級(jí)類加載器
- 能找到這個(gè)類,由上級(jí)加載,加載后該類也對(duì)下級(jí)加載器可見
- 找不到這個(gè)類,則下級(jí)類加載器才有資格執(zhí)行加載
雙親委派的目的有兩點(diǎn)
-
讓上級(jí)類加載器中的類對(duì)下級(jí)共享(反之不行),即能讓你的類能依賴到 jdk 提供的核心類 (反之不行:jdk肯定不需要依賴你自己寫的類)
-
讓類的加載有優(yōu)先次序,保證核心類優(yōu)先加載
上級(jí)類加載器中的類對(duì)下級(jí)可見
但是下級(jí)類加載器中的類對(duì)上級(jí)不可見
對(duì)雙親委派的誤解
下面面試題的回答是錯(cuò)誤的
錯(cuò)在哪了?
-
自己編寫類加載器就能加載一個(gè)假冒的 java.lang.System 嗎? 答案是不行。
-
假設(shè)你自己的類加載器用了雙親委派,那么優(yōu)先由啟動(dòng)類加載器加載真正的 java.lang.System,自然不會(huì)加載假冒的
-
假設(shè)你自己的類加載器不用雙親委派,那么你的類加載器加載假冒的 java.lang.System 時(shí),它需要先加載父類 java.lang.Object,而你沒有用委派,找不到 java.lang.Object 所以加載會(huì)失敗
-
以上也僅僅是假設(shè)。事實(shí)上操作你就會(huì)發(fā)現(xiàn),自定義類加載器加載以 java. 打頭的類時(shí),會(huì)拋安全異常,在 jdk9 以上版本這些特殊包名都與模塊進(jìn)行了綁定,更連編譯都過(guò)不了 (實(shí)際操作,直接拋安全異常,或者編譯不過(guò),到不了假設(shè)那一步,jdk已經(jīng)做了安全措施,防止你這么做了, 直接就不允許你重復(fù)寫java.lang這重包名了)
代碼說(shuō)明
- day03.loader.TestJdk9ClassLoader - 演示類加載器與模塊的綁定關(guān)系 =》 結(jié)論:不準(zhǔn)自己重復(fù)寫jdk已經(jīng)有的包名.類名
6. 四種引用
要求
- 掌握四種引用
強(qiáng)引用
-
普通變量賦值即為強(qiáng)引用,如 A a = new A();
-
通過(guò) GC Root 的引用鏈,如果強(qiáng)引用不到該對(duì)象,該對(duì)象才能被回收
軟引用(SoftReference)
-
例如:SoftReference a = new SoftReference(new A()); (中間有一個(gè)SoftReference對(duì)象做中轉(zhuǎn),a間接關(guān)聯(lián)到對(duì)象new A())
-
如果僅有軟引用該對(duì)象時(shí),首次垃圾回收不會(huì)回收該對(duì)象,如果內(nèi)存仍不足,再次回收時(shí)才會(huì)釋放對(duì)象 (內(nèi)存不足時(shí)會(huì)觸發(fā)GC,第一次饒過(guò)你,第二次內(nèi)存不足又觸發(fā)了GC, 是會(huì)將軟引用對(duì)象回收的(有強(qiáng)引用指向的對(duì)象GC無(wú)法回收))
-
軟引用自身需要配合引用隊(duì)列來(lái)釋放(如下圖,a對(duì)象是軟引用,但是SoftReference自身還是強(qiáng)引用,GC無(wú)法回收軟引用自身)
-
典型例子是反射數(shù)據(jù)(通過(guò)反射獲取的數(shù)據(jù)都是軟引用數(shù)據(jù),如:類名.class=》獲取的成員變量,方法等數(shù)據(jù)信息都是軟引用)
弱引用(WeakReference)
-
例如:WeakReference a = new WeakReference(new A());
-
如果僅有弱引用引用該對(duì)象時(shí),只要發(fā)生垃圾回收,就會(huì)釋放該對(duì)象
-
弱引用自身需要配合引用隊(duì)列來(lái)釋放 (同上)
-
典型例子是 ThreadLocalMap 中的 Entry 對(duì)象
虛引用(PhantomReference)
-
例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);
-
必須配合引用隊(duì)列一起使用,當(dāng)虛引用所引用的對(duì)象被回收時(shí),由 Reference Handler 線程將虛引用對(duì)象入隊(duì),這樣就可以知道哪些對(duì)象被回收,從而對(duì)它們關(guān)聯(lián)的資源做進(jìn)一步處理
-
典型例子是 Cleaner 釋放 DirectByteBuffer 關(guān)聯(lián)的直接內(nèi)存
引用隊(duì)列詳解:如圖,虛引用關(guān)聯(lián)的對(duì)象a,b被釋放內(nèi)存后,虛引用本身會(huì)被放到引用隊(duì)列里,由Reference Handler 線程專門負(fù)責(zé)回收他們,因?yàn)樗麄兛赡苓€關(guān)聯(lián)了其他一些資源(不僅僅只是a對(duì)象和b對(duì)象)
代碼說(shuō)明
- day03.reference.TestPhantomReference - 演示虛引用的基本用法
- day03.reference.TestWeakReference - 模擬 ThreadLocalMap, 采用引用隊(duì)列釋放 entry 內(nèi)存
String str = new String("hello"); // "hello"在堆內(nèi)存中 (new出來(lái)的都在堆中)
String str = "hello"; // "hello" 在常量池中
ThreadLocalMap 中的 Entry 對(duì)象,key是弱引用,value是強(qiáng)引用
上圖就是一種典型的內(nèi)存泄露
解決:使用引用隊(duì)列,將Entry和某個(gè)引用隊(duì)列關(guān)聯(lián)上,當(dāng)Entry的key被回收時(shí),整個(gè)Entry對(duì)象會(huì)被放到引用隊(duì)列里面去,然后直接將已經(jīng)在引用隊(duì)列中的Entry對(duì)象的Map引用去掉就行了(或者說(shuō)看看當(dāng)前Entry在不在Map中,在就將Map里面記錄Entry的數(shù)組對(duì)應(yīng)引用設(shè)置為null),沒有引用指向它,下次回收時(shí)就會(huì)被回收了
jdk不是這么實(shí)現(xiàn)的,成本會(huì)比較高
★★★key就是ThreadLocal對(duì)象本身,線程運(yùn)行時(shí)一定還被其他對(duì)象強(qiáng)引用,所以不怕他被設(shè)置為弱引用,線程沒有結(jié)束前,key(有其他強(qiáng)引用)不會(huì)被釋放。但是value一旦設(shè)置為弱引用,真的就只有這一個(gè)弱引用了,很可能線程還沒結(jié)束,就被GC回收了?!铩铩?/mark>
7. finalize
要求
- 掌握 finalize 的工作原理與缺點(diǎn)
finalize
- 一般回答:它是 Object 中的一個(gè)方法,如果子類重寫它,垃圾回收時(shí)此方法會(huì)被調(diào)用,可以在其中進(jìn)行資源釋放和清理工作
- 優(yōu)秀回答:將資源釋放和清理放在 finalize 方法中非常不好,非常影響性能,嚴(yán)重時(shí)甚至?xí)?OOM(Out of Memory),從 Java9 開始就被標(biāo)注為 @Deprecated,不建議被使用了
追問(wèn):為什么非常不好,非常影響性能?
見下面原理:
補(bǔ):守護(hù)線程,在主線程已經(jīng)結(jié)束時(shí),守護(hù)線程就不會(huì)再執(zhí)行了(即使有代碼沒執(zhí)行完畢)
finalize 原理
- 對(duì) finalize 方法進(jìn)行處理的核心邏輯位于 java.lang.ref.Finalizer 類中,它包含了名為 unfinalized 的靜態(tài)變量(雙向鏈表結(jié)構(gòu)),F(xiàn)inalizer 也可被視為另一種引用對(duì)象(地位與軟、弱、虛相當(dāng),只是不對(duì)外,無(wú)法直接使用)
- 當(dāng)重寫了 finalize 方法的對(duì)象,在構(gòu)造方法調(diào)用之時(shí),JVM 都會(huì)將其包裝成一個(gè) Finalizer 對(duì)象,并加入 unfinalized 鏈表中 (表示這些對(duì)象的finalize方法還沒有被調(diào)用哦,不要輕易釋放它 (也是此引用鏈的作用))
- Finalizer 類中還有另一個(gè)重要的靜態(tài)變量,即 ReferenceQueue 引用隊(duì)列 (類似前面四種引用里面的引用隊(duì)列,輔助釋放引用對(duì)象本身(幫助釋放關(guān)聯(lián)的一些其他資源) 區(qū)別在于加入隊(duì)列時(shí)關(guān)聯(lián)的對(duì)象暫時(shí)不能被回收,因?yàn)橐日{(diào)用 finalize 方法),剛開始它是空的。當(dāng)狗對(duì)象可以被當(dāng)作垃圾回收時(shí),就會(huì)把這些狗對(duì)象對(duì)應(yīng)的 Finalizer 對(duì)象加入此引用隊(duì)列
- 但此時(shí) Dog 對(duì)象還沒法被立刻回收,因?yàn)?unfinalized -> Finalizer 這一引用鏈還在引用它嘛,為的是【先別著急回收啊,等我調(diào)完 finalize 方法,再回收】
- FinalizerThread 線程會(huì)從 ReferenceQueue 中逐一取出每個(gè) Finalizer 對(duì)象,把它們從鏈表斷開并真正調(diào)用 finallize 方法
- 由于整個(gè) Finalizer 對(duì)象已經(jīng)從 unfinalized 鏈表中斷開,這樣沒誰(shuí)能引用到它和狗對(duì)象,所以下次 gc 時(shí)就被回收了
finalize 缺點(diǎn)
- 無(wú)法保證資源釋放:FinalizerThread 是守護(hù)線程,代碼很有可能沒來(lái)得及執(zhí)行完,線程就結(jié)束了
- 無(wú)法判斷是否發(fā)生錯(cuò)誤:執(zhí)行 finalize 方法時(shí),會(huì)吞掉任意異常(Throwable try-catch給吞了)
- 內(nèi)存釋放不及時(shí):重寫了 finalize 方法的對(duì)象在第一次被 gc 時(shí),并不能及時(shí)釋放它占用的內(nèi)存,因?yàn)橐戎?FinalizerThread 調(diào)用完 finalize,把它從 unfinalized 隊(duì)列移除后,第二次 gc 時(shí)才能真正釋放內(nèi)存
- 有的文章提到【Finalizer 線程會(huì)和我們的主線程進(jìn)行競(jìng)爭(zhēng),不過(guò)由于它的優(yōu)先級(jí)較低,獲取到的CPU時(shí)間較少,因此它永遠(yuǎn)也趕不上主線程的步伐】這個(gè)顯然是錯(cuò)誤的,F(xiàn)inalizerThread 的優(yōu)先級(jí)較普通線程更高(max-2=8 普通線程都才5),原因應(yīng)該是 finalize 串行執(zhí)行慢等原因綜合導(dǎo)致(隊(duì)列上取一個(gè)調(diào)用一個(gè)finalize )
代碼說(shuō)明
- day03.reference.TestFinalize - finalize 的測(cè)試代碼
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-491341.html
到了這里,關(guān)于java面經(jīng)03-虛擬機(jī)篇-jvm內(nèi)存結(jié)構(gòu)&垃圾回收、內(nèi)存溢出&類加載、引用&悲觀鎖&HashTable、引用&finalize的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!