1 判斷對象是否可回收有幾種方式霹崎?
- 引用計(jì)數(shù)算法
優(yōu)點(diǎn):實(shí)現(xiàn)簡單桑驱,判定高效;
缺點(diǎn):很難解決對象之間相互循環(huán)引用的問題脉顿;
- 可達(dá)性分析算法
通過一系列"GC Roots"對象作為起始點(diǎn),開始向下搜索点寥,當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí)(從GC Roots到這個(gè)對象不可達(dá))艾疟,則證明該對象是不可用的;
優(yōu)點(diǎn):更加精確和嚴(yán)謹(jǐn)敢辩,可以分析出循環(huán)數(shù)據(jù)結(jié)構(gòu)相互引用的情況蔽莱;
缺點(diǎn):實(shí)現(xiàn)比較復(fù)雜;需要分析大量數(shù)據(jù)责鳍,消耗大量時(shí)間碾褂;分析過程需要GC停頓(引用關(guān)系不能發(fā)生變化),即停頓所有Java執(zhí)行線程(稱為"Stop The World"历葛,是垃圾回收重點(diǎn)關(guān)注的問題)正塌;
2 "GC Roots"對象都包含哪些
- 虛擬機(jī)棧 (棧幀中本地變量表)中引用的對象;
- 方法區(qū)中 類靜態(tài)屬性引用的對象 恤溶;
- 方法區(qū)中 常量引用的對象乓诽;
- 本地方法棧 JNI(Native方法)中引用的對象;
3 Java四種引用類型分別是什么咒程?及存活時(shí)間
- 強(qiáng)引用:程序代碼普遍存在的鸠天,類似"Object obj=new Object()";只要強(qiáng)引用還存在帐姻,GC永遠(yuǎn)不會(huì)回收被引用的對象稠集;
- 軟引用:描述還有用但并非必需的對象;直到內(nèi)存空間不夠時(shí)(拋出OutOfMemoryError之前)饥瓷,才會(huì)被垃圾回收剥纷;最常用于實(shí)現(xiàn)對內(nèi)存敏感的緩存;SoftReference類實(shí)現(xiàn)呢铆;
- 弱引用:用來描述非必需對象晦鞋;只能生存到下一次垃圾回收之前,無論內(nèi)存是否足夠;WeakReference類實(shí)現(xiàn)悠垛;
- 虛引用:完全不會(huì)對其生存時(shí)間構(gòu)成影響线定;唯一目的就是能在這個(gè)對象被回收時(shí)收到一個(gè)系統(tǒng)通知;PhantomRenference類實(shí)現(xiàn)确买;
4 Java四種引用使用場景
- 強(qiáng)引用-FinalReference
地球人都知道斤讥,但是我講不出來;
- 軟引用-SoftReference
創(chuàng)建緩存的時(shí)候拇惋,創(chuàng)建的對象放進(jìn)緩存中周偎,當(dāng)內(nèi)存不足時(shí),JVM就會(huì)回收早先創(chuàng)建的對象撑帖。PS:圖片編輯器,視頻編輯器之類的軟件可以使用這種思路澳眷。
軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用胡嘿,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中钳踊。
- 弱引用-WeakReference
Java源碼中的java.util.WeakHashMap中的key就是使用弱引用衷敌,一旦不需要某個(gè)引用,JVM會(huì)自動(dòng)處理它拓瞪,這樣就不需要做其它操作缴罗。
弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收祭埂,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中面氓。
- 虛引用-PhantomReference
主要用來跟蹤對象被垃圾回收器回收的活動(dòng)。虛引用的回收機(jī)制跟弱引用差不多蛆橡,但是它被回收之前舌界,會(huì)被放入ReferenceQueue中。注意哦泰演,其它引用是被JVM回收后才被傳入ReferenceQueue中的呻拌。由于這個(gè)機(jī)制,所以虛引用大多被用于引用銷毀前的處理工作睦焕。
程序可以通過判斷引用隊(duì)列中是否已經(jīng)加入了虛引用藐握,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列垃喊,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動(dòng)猾普。
對象銷毀前的一些操作,比如說資源釋放等缔御。Object.finalize()雖然也可以做這類動(dòng)作抬闷,但是這個(gè)方式即不安全又低效。
5 JVM如何進(jìn)行對象標(biāo)記
- 第一次標(biāo)記:在可達(dá)性分析后發(fā)現(xiàn)到GC Roots沒有任何引用鏈相連時(shí),被第一次標(biāo)記笤成;并且進(jìn)行一次篩選:此對象是否必要執(zhí)行finalize()方法评架;沒有必要執(zhí)行的情況,則標(biāo)記對象已死炕泳;有必要執(zhí)行的情況纵诞,則對象被放入F-Queue隊(duì)列中;
- 第二次標(biāo)記:GC將對F-Queue隊(duì)列中的對象進(jìn)行第二次小規(guī)模標(biāo)記培遵;finalize()方法是對象逃脫死亡的最后一次機(jī)會(huì)浙芙;一個(gè)對象的finalize()方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次,經(jīng)過finalize()方法逃脫死亡的對象籽腕,第二次不會(huì)再調(diào)用嗡呼;
6 為何不建議使用finalize()方法
因?yàn)槠鋱?zhí)行的時(shí)間不確定,甚至是否被執(zhí)行也不確定(Java程序的不正常退出)皇耗,而且運(yùn)行代價(jià)高昂南窗,無法保證各個(gè)對象的調(diào)用順序(甚至有不同線程中調(diào)用);如果需要"釋放資源"郎楼,可以定義顯式的終止方法万伤,并在"try-catch-finally"的finally{}塊中保證及時(shí)調(diào)用;
如果有關(guān)鍵資源呜袁,必須顯式的終止方法敌买;一般情況下,應(yīng)盡量避免使用它阶界,甚至可以忘掉它虹钮;
7 什么是安全點(diǎn),為什么需要
運(yùn)行中荐操,非常多的指令都會(huì)導(dǎo)致引用關(guān)系變化芜抒;如果為這些指令都生成對應(yīng)的OopMap,需要的空間成本太高托启;
只在特定的位置記錄OopMap引用關(guān)系宅倒,這些位置稱為安全點(diǎn)(Safepoint);
8 如何選定安全點(diǎn)
不能太少屯耸,否則GC等待時(shí)間太長拐迁;也不能太多,否則GC過于頻繁疗绣,增大運(yùn)行時(shí)負(fù)荷线召;
所以,基本上是以程序"是否具有讓程序長時(shí)間執(zhí)行的特征"為標(biāo)準(zhǔn)選定多矮,如:方法調(diào)用缓淹、循環(huán)跳轉(zhuǎn)哈打、循環(huán)的末尾、異常跳轉(zhuǎn)等讯壶;
只有具有這些功能的指令才會(huì)產(chǎn)生Safepoint料仗;
9 如何使Java線程在安全點(diǎn)上停頓
- 搶先式中斷(Preemptive Suspension):在GC發(fā)生時(shí),首先中斷所有線程伏蚊;如果發(fā)現(xiàn)不在Safepoint上的線程立轧,就恢復(fù)讓其運(yùn)行到Safepoint上;
- 主動(dòng)式中斷(Voluntary Suspension):在GC發(fā)生時(shí)躏吊,不直接操作線程中斷氛改,而是僅簡單設(shè)置一個(gè)標(biāo)志;讓各線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志比伏,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷掛起胜卤;
- 而輪詢標(biāo)志的地方和Safepoint是重合的;
10 什么是安全區(qū)域赁项,為什么需要安全區(qū)域
線程不執(zhí)行時(shí)沒有CPU時(shí)間(Sleep或Blocked狀態(tài))瑰艘,無法運(yùn)行到Safepoint上再中斷掛起;
安全區(qū)域:指一段代碼片段中肤舞,引用關(guān)系不會(huì)發(fā)生變化;在這個(gè)區(qū)域中的任意地方開始GC都是安全的均蜜;
11 如何使用安全區(qū)域解決問題
- 線程執(zhí)行進(jìn)入Safe Region李剖,首先標(biāo)識(shí)自己已經(jīng)進(jìn)入Safe Region;
- 線程被喚醒離開Safe Region時(shí)囤耳,其需要檢查系統(tǒng)是否已經(jīng)完成根節(jié)點(diǎn)枚舉(或整個(gè)GC)篙顺;
- 如果已經(jīng)完成,就繼續(xù)執(zhí)行充择;否則必須等待德玫,直到收到可以安全離開Safe Region的信號通知,這樣就不會(huì)影響標(biāo)記結(jié)果椎麦;
12 GC算法:標(biāo)記-清楚優(yōu)缺點(diǎn)
優(yōu)點(diǎn):基于最基礎(chǔ)的可達(dá)性分析算法宰僧,它是最基礎(chǔ)的收集算法;而后續(xù)的收集算法都是基于這種思路并對其不足進(jìn)行改進(jìn)得到的观挎;
缺點(diǎn):效率問題琴儿,標(biāo)記和清除兩個(gè)過程的效率都不高;空間問題嘁捷,標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片造成;這會(huì)導(dǎo)致分配大內(nèi)存對象時(shí),無法找到足夠的連續(xù)內(nèi)存雄嚣;從而需要提前觸發(fā)另一次垃圾收集動(dòng)作晒屎;
13 GC算法:復(fù)制算法優(yōu)缺點(diǎn)
優(yōu)點(diǎn):使得每次都是只對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收;內(nèi)存分配時(shí)也不用考慮內(nèi)存碎片等問題;實(shí)現(xiàn)簡單鼓鲁,運(yùn)行高效蕴轨;
缺點(diǎn):空間浪費(fèi);效率隨對象存活率升高而變低坐桩;
14 GC算法:HotSpot虛擬機(jī)復(fù)制算法
- 將新生代內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間尺棋;
- 每次使用Eden和其中一塊Survivor;
- 當(dāng)回收時(shí)绵跷,將Eden和使用中的Survivor中還存活的對象一次性復(fù)制到另外一塊Survivor膘螟;
- 而后清理掉Eden和使用過的Survivor空間;
- 后面就使用Eden和復(fù)制到的那一塊Survivor空間碾局,重復(fù)步驟3荆残;
默認(rèn)Eden:Survivor=8:1,即每次可以使用90%的空間净当,只有一塊Survivor的空間被浪費(fèi)内斯;
15 什么是分配擔(dān)保
如果另一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象時(shí),這些對象將直接通過分配擔(dān)保機(jī)制(Handle Promotion)進(jìn)入老年代像啼;
16 GC算法:標(biāo)記-整理優(yōu)缺點(diǎn)
優(yōu)點(diǎn):不會(huì)產(chǎn)生內(nèi)存碎片俘闯;
缺點(diǎn):增加了對存活對象需要整理的過程,效率更低忽冻;
17 分代收集算法
"分代收集"(Generational Collection)算法結(jié)合不同的收集算法處理不同區(qū)域真朗。
新生代:每次垃圾收集都有大批對象死去,只有少量存活僧诚;所以可采用復(fù)制算法遮婶;
老年代:對象存活率高,沒有額外的空間可以分配擔(dān)保湖笨;使用"標(biāo)記-清理"或"標(biāo)記-整理"算法旗扑;
優(yōu)點(diǎn):根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ǎ?/p>
缺點(diǎn):仍然不能控制每次垃圾收集的時(shí)間;
18 G1垃圾收集算法
19 JVM有哪些收集器慈省?分別用于哪些代臀防?
JDK7/8后,HotSpot虛擬機(jī)所有收集器及組合(連線)辫呻,如下圖:
新生代收集器:Serial清钥、ParNew、Parallel Scavenge放闺;
老年代收集器:Serial Old祟昭、Parallel Old、CMS怖侦;
整堆收集器:G1篡悟;
20 Serial收集器
新生代谜叹、復(fù)制算法、單線程收集搬葬;
缺點(diǎn):進(jìn)行垃圾收集時(shí)份招,必須暫停所有工作線程摸航,直到完成;即會(huì)"Stop The World";
Serial/Serial Old組合收集器運(yùn)行示意圖如下:
21 ParNew收集器
新生代励背、復(fù)制算法替饿、多線程收集怜珍;
缺點(diǎn):進(jìn)行垃圾收集時(shí)瞬测,必須暫停所有工作線程,直到完成床三;即會(huì)"Stop The World"一罩;
ParNew/Serial Old組合收集器運(yùn)行示意圖如下:
22 Parallel Scavenge收集器
新生代、復(fù)制算法撇簿、多線程收集聂渊;
CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間;而Parallel Scavenge收集器的目標(biāo)則是達(dá)一個(gè)可控制的吞吐量(Throughput)四瘫;
23 Serial Old收集器
老年代汉嗽、"標(biāo)記-整理"算法(還有壓縮,Mark-Sweep-Compact)找蜜、單線程收集诊胞;
24 Parallel Old收集器
老年代、"標(biāo)記-整理"算法(還有壓縮锹杈,Mark-Sweep-Compact)、多線程收集迈着;
25 CMS收集器
老年代竭望、"標(biāo)記-清除"算法(不進(jìn)行壓縮操作,產(chǎn)生內(nèi)存碎片)裕菠、并發(fā)收集咬清、低停頓
CMS收集器運(yùn)行示意圖如下:
整個(gè)過程中耗時(shí)最長的并發(fā)標(biāo)記和并發(fā)清除都可以與用戶線程一起工作;所以總體上說奴潘,CMS收集器的內(nèi)存回收過程與用戶線程一起并發(fā)執(zhí)行旧烧;
CMS收集器3個(gè)明顯的缺點(diǎn):
- 對CPU資源非常敏感;
- 無法處理浮動(dòng)垃圾画髓,可能出現(xiàn)"Concurrent Mode Failure"失斁蚣簟;
- 產(chǎn)生大量內(nèi)存碎片奈虾;
26 G1收集器
27 JVM如何進(jìn)行對象內(nèi)存分配
在堆上分配(JIT編譯優(yōu)化后可能在棧上分配)夺谁,主要在新生代的Eden區(qū)中分配廉赔;
如果啟用了本地線程分配緩沖,將線程優(yōu)先在TLAB上分配匾鸥;
少數(shù)情況下蜡塌,可能直接分配在老年代中;
分配的細(xì)節(jié)取決于當(dāng)前使用哪種垃圾收集器組合勿负,以及JVM中內(nèi)存相關(guān)參數(shù)設(shè)置馏艾;
28 哪些情況下對象內(nèi)存分配會(huì)直接進(jìn)入老年代
- 當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),JVM將發(fā)起一次Minor GC(新生代GC)奴愉;Minor GC時(shí)琅摩,如果發(fā)現(xiàn)存活的對象無法全部放入Survivor空間,只好通過分配擔(dān)保機(jī)制提前轉(zhuǎn)移到老年代躁劣。
- 需要大量連續(xù)內(nèi)存空間的Java大對象會(huì)直接進(jìn)入老年代迫吐,容易提前觸發(fā)老年代GC;
- 經(jīng)過多次Minor GC账忘,如果年齡達(dá)到一定程度志膀,就晉升到老年代;
- 動(dòng)態(tài)對象年齡判定:如果在Survivor空間中相同年齡的所有對象大小總和大于Survivor空間的一半鳖擒,大于或等于該年齡的對象就可以直接進(jìn)入老年代溉浙;
29 方法區(qū)中可回收哪些對象
- 廢棄常量:與回收J(rèn)ava堆中對象非常類似;
- 無用的類:(1)該類所有實(shí)例都已經(jīng)被回收(即Java椎中不存在該類的任何實(shí)例)蒋荚;(2)加載該類的ClassLoader已經(jīng)被回收戳稽,也即通過引導(dǎo)程序加載器加載的類不能被回收;(3)該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用期升,無法在任何地方通過反射訪問該類的方法惊奇;
30 JDK HotSpot虛擬機(jī)方法區(qū)調(diào)整
- 在JDK7中,使用永久代(Permanent Generation)實(shí)現(xiàn)方法區(qū)播赁,這樣就可以不用專門實(shí)現(xiàn)方法區(qū)的內(nèi)存管理颂郎,但這容易引起內(nèi)存溢出問題;
- 在JDK8中容为,永久代已被刪除乓序,類元數(shù)據(jù)(Class Metadata)存儲(chǔ)空間直接在本地內(nèi)存中分配;