一尺上、垃圾收集的意義
?相對于C++來說寇壳,Java預言顯著的特點就是引入了垃圾回收機制太伊,它使得Java程序員在編寫程序的時候不在需要考慮內(nèi)存管理。由于垃圾回收機制箫锤,Java中的對象不再有“作用域”的概念贬蛙,只有對象的引用才有作用域。垃圾回收機制可以有效的防止內(nèi)存泄露谚攒,有效的使用空閑的內(nèi)存速客。
?內(nèi)存泄露是指該內(nèi)存空間使用完畢之后未回收,在不涉及復雜數(shù)據(jù)結(jié)構(gòu)的一般情況下五鲫,Java 的內(nèi)存泄露表現(xiàn)為一個內(nèi)存對象的生命周期超出了程序需要它的時間長度溺职,我們有時也將其稱為“對象游離”。
?Java 垃圾回收機制要考慮的問題很復雜位喂,本文闡述了其三個核心問題浪耘,包括:
那些內(nèi)存需要回收?(對象是否可以被回收的兩種經(jīng)典算法: 引用計數(shù)法 和 可達性分析算法)
什么時候回收塑崖? (堆的新生代七冲、老年代、永久代的垃圾回收時機规婆,MinorGC 和 FullGC)
如何回收澜躺?(三種經(jīng)典垃圾回收算法(標記清除算法、復制算法抒蚜、標記整理算法)及分代收集算法 和 七種垃圾收集器)
二掘鄙、如何確定一個對象是否會被回收
2.1 引用計數(shù)算法(Reference Counting)
? 引用計數(shù)算法是通過判斷對象的引用數(shù)量來決定對象是否可以被回收。
它的思路是給對象中添加一個引用計數(shù)器嗡髓,每當有一個地方引用它時操漠,計數(shù)器值就加1;當引用失效時饿这,計數(shù)器值就減1浊伙;任何時刻計數(shù)器為0的對象就是不可能再被使用的撞秋。
?大部分場景下,這個算法都是不錯嚣鄙,效率也比較高吻贿;但是Java虛擬機里面沒有選用引用計數(shù)算法來管理內(nèi)存,其中最主要的原因是它很難解決對象之間相互循環(huán)引用的問題哑子。
package com.sunny.jdk.gc;
/**
* <Description> 引用計數(shù)算法的缺陷<br>
*
* @author Sunny<br>
* @version 1.0<br>
* @taskId: <br>
* @createDate 2018/09/06 14:01 <br>
* @see com.sunny.jdk.gc <br>
*/
public class ReferenceCountingGC {
public Object instance = null;
public static final int _1MB = 1024 * 1024;
/**
* 占點內(nèi)存舅列,以便GC日志觀看
*/
private byte[] bigSize = new byte[2 * _1MB];
public static void main(String[] args) {
testGC();
}
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//這里發(fā)生GC, objA 和 objB能否被回收赵抢?
System.gc();
}
}
? 上述代碼最后面兩句將objA和objB賦值為null剧蹂,也就是說objA和objB指向的對象已經(jīng)不可能再被訪問,但是由于它們互相引用對方烦却,導致它們的引用計數(shù)器都不為 0宠叼,那么垃圾收集器就永遠不會回收它們。
2.2 可達性分析算法(Reachability Analysis)
?判斷對象的引用鏈是否可達
其爵。它的思路是:通過一系列的稱為“GC Roots”的對象作為起始點冒冬,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)摩渺,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說就是從 GC Roots 到這個對象不可達)時简烤,則證明此對象是不可用的,如圖所示:
在Java中摇幻,可作為 GC Root 的對象包括以下幾種:
虛擬機棧(棧幀中的局部變量表)中引用的對象横侦;
方法區(qū)中類靜態(tài)屬性引用的對象;
方法區(qū)中常量引用的對象绰姻;
本地方法棧中Native方法引用的對象枉侧;
三、垃圾回收算法
?垃圾收集算法主要有:標記-清除算法(Mark-Sweep)狂芋、復制算法(Copying)榨馁、標記-整理算法(Mark-Compact)、分帶收集算法(Generational Collection)帜矾。
3.1 標記-清除算法
?“標記-清除”算法是最基礎(chǔ)的算法翼虫,它分為“標記”和清除兩個階段:首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象
屡萤;
?缺點: 一個是效率問題珍剑,標記和清除兩個過程的效率都不高;另一個是空間問題灭衷,標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片次慢,空間碎片太多可能會導致以后再程序運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作翔曲。
3.2 復制算法
?復制算法的原理:它將可用內(nèi)存按容量劃分為大小相等的兩塊迫像,每次只使用其中的一塊。當這一塊的內(nèi)存用完了瞳遍,就將還存活著的對象復制到另外一塊上面闻妓,然后再把已使用過的內(nèi)存空間一次清理掉。
這種算法適用于對象存活率低的場景掠械,比如新生代由缆。這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復雜情況猾蒂,只要移動堆頂指針均唉,按順序分配內(nèi)存即可,實現(xiàn)簡單肚菠,運行高效舔箭。該算法示意圖如下所示:
?將現(xiàn)有的內(nèi)存空間分為兩快,每次只使用其中一塊蚊逢,在垃圾回收時將正在使用的內(nèi)存中的存活對象復制到未被使用的內(nèi)存塊中层扶,之后,清除正在使用的內(nèi)存塊中的所有對象烙荷,交換兩個內(nèi)存的角色镜会,完成垃圾回收。如果系統(tǒng)中的垃圾對象很多终抽,復制算法需要復制的存活對象數(shù)量并不會太大戳表。因此在真正需要垃圾回收的時刻,復制算法的效率是很高的昼伴。又由于對象在垃圾回收過程中統(tǒng)一被復制到新的內(nèi)存空間中匾旭,因此,可確蹦堵耄回收后的內(nèi)存空間是沒有碎片的季率。
復制算法的高效性是建立在存活對象少、垃圾對象多的前提下的描沟。這種情況在年輕代經(jīng)常發(fā)生飒泻,但是在老年代更常見的情況是大部分對象都是存活對象。如果依然使用復制算法吏廉,由于存活的對象較多泞遗,復制的成本也將很高。
該算法的缺點是將系統(tǒng)內(nèi)存折半席覆。
?Java 的新生代串行垃圾回收器中使用了復制算法的思想史辙。新生代分為 eden 空間、from 空間、to 空間 3 個部分聊倔。其中 from 空間和 to 空間可以視為用于復制的兩塊大小相同晦毙、地位相等,且可進行角色互換的空間塊耙蔑。from 和 to 空間也稱為 survivor 空間见妒,即幸存者空間,用于存放未被回收的對象甸陌。在垃圾回收時须揣,eden 空間中的存活對象會被復制到未使用的 survivor 空間中 (假設(shè)是 to),正在使用的 survivor 空間 (假設(shè)是 from) 中的年輕對象也會被復制到 to 空間中 (大對象钱豁,或者老年對象會直接進入老年帶耻卡,如果 to 空間已滿,則對象也會直接進入老年代)牲尺。此時卵酪,eden 空間和 from 空間中的剩余對象就是垃圾對象,可以直接清空秸谢,to 空間則存放此次回收后的存活對象凛澎。這種改進的復制算法既保證了空間的連續(xù)性,又避免了大量的內(nèi)存空間浪費估蹄。
3.3 標記-整理算法
?復制收集算法在對象存活率較高時就要進行較多的復制操作塑煎,效率將會變低。如果不想浪費50%的空間臭蚁,就需要有額外的空間進行分配擔保最铁,以應對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以老年代不能直接選用這種算法垮兑。
?標記整理算法冷尉,標記過程仍然與“標記-清除”算法一樣,但是后續(xù)步驟不是直接對可回收對象進行清理系枪,而是讓所有存活的對象都向一端移動雀哨,然后直接清理掉端邊界以外的內(nèi)存,
“標記-整理”算法的示意圖如下:
3.4 分代收集算法
?對于一個大型的系統(tǒng)私爷,當創(chuàng)建的對象和方法變量比較多時雾棺,堆內(nèi)存中的對象也會比較多,如果逐一分析對象是否該回收衬浑,那么勢必造成效率低下捌浩。分代收集算法是基于這樣一個事實:不同的對象的生命周期(存活情況)是不一樣的,而不同生命周期的對象位于堆中不同的區(qū)域工秩,因此對堆內(nèi)存不同區(qū)域采用不同的策略進行回收可以提高 JVM 的執(zhí)行效率尸饺。
當代商用虛擬機使用的都是分代收集算法:新生代對象存活率低进统,就采用復制算法;老年代存活率高浪听,就用標記清除算法或者標記整理算法螟碎。Java堆內(nèi)存一般可以分為新生代、老年代和永久代三個模塊馋辈,如下圖所示:
具體分代收集算法可以參考:分代收集算法
四抚芦、 HotSpot的算法實現(xiàn)
?前面兩大節(jié)主要從理論上介紹了對象存活判定算法和垃圾收集算法倍谜,而在HotSpot虛擬機上實現(xiàn)這些算法時迈螟,必須對算法的執(zhí)行效率有嚴格的考量,才能保證虛擬機高效運行尔崔。
4.1枚舉根節(jié)點
?從可達性分析中從GC Roots節(jié)點找引用鏈
這個操作為例答毫,可作為GC Roots的節(jié)點主要在全局性的引用
(例如常量或類靜態(tài)屬性)與執(zhí)行上下文
(例如棧幀中的局部變量表)中,現(xiàn)在很多應用僅僅方法區(qū)就有數(shù)百兆季春,如果要逐個檢查這里面的引用洗搂,那么必然會消耗很多時間。
4.2 GC停頓("Stop The World")
?另外载弄,可達性分析工作必須在一個能確保一致性的快照
中進行——這里一致性
的意思是指在整個分析期間整個執(zhí)行系統(tǒng)看起來就像被凍結(jié)在某個時間點上
耘拇,不可以出現(xiàn)分析過程中對象引用關(guān)系還在不斷變化的情況,這是保證分析結(jié)果準確性的基礎(chǔ)宇攻。這點是導致GC進行時必須停頓所有Java執(zhí)行線程
(Sun將這件事情稱為Stop The World
)的其中一個重要原因惫叛,即使是在號稱(幾乎)不會發(fā)生停頓的CMS收集器中,枚舉根節(jié)點時也是必須要停頓的逞刷。
4.3 準確式GC與OopMap
?由于目前的主流Java虛擬機使用的都是準確式GC(即使用準確式內(nèi)存管理嘉涌,虛擬機可用知道內(nèi)存中某個位置的數(shù)據(jù)具體是什么類型)
,所以當執(zhí)行系統(tǒng)停頓下來后夸浅,并不需要一個不漏地檢查完所有執(zhí)行上下文和全局的引用位置仑最,虛擬機應當是有辦法直接得知哪些地方存放著對象引用。在HotSpot的實現(xiàn)中帆喇,是使用一組稱為OopMap
的數(shù)據(jù)結(jié)構(gòu)來達到這個目的的警医,在類加載完成的時候,HotSpot就把對象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)
計算出來坯钦,在JIT編譯過程中预皇,也會在特定的位置記錄下棧和寄存器中哪些位置是引用
。這樣葫笼,GC在掃描時就可以直接得知這些信息了深啤。
4.4 安全點(Safepoint)——進行GC時程序停頓的位置
?在OopMap的協(xié)助下,HotSpot可以快速且準確地完成GC Roots枚舉路星,但一個很現(xiàn)實的問題隨之而來:可能導致引用關(guān)系變化溯街,或者說OopMap內(nèi)容變化的指令非常多诱桂,如果為每一條指令都生成對應的OopMap,那將會需要大量的額外空間呈昔,這樣GC的空間成本將會變得很高挥等。
?為此,HotSpot選擇不為每條指令都生成OopMap堤尾,而是只在“特定的位置”記錄這些信息肝劲,這些位置便被稱為安全點(Safepoint)
。也就是說郭宝,程序執(zhí)行時并非在所有地方都能停頓下來開始GC辞槐,只有在到達安全點時才能暫停
。Safepoint的選定既不能太少以致于讓GC等待時間太長粘室,也不能過于頻繁以致于過分增大運行時的負荷榄檬。所以,安全點的選定基本上是以程序“是否具有讓程序長時間執(zhí)行的特征”
為標準進行選定的——因為每條指令執(zhí)行的時間都非常短暫衔统,程序不太可能因為指令流長度太長這個原因而過長時間運行鹿榜,“長時間執(zhí)行”的最明顯特征就是指令序列復用
,例如方法調(diào)用
锦爵、循環(huán)跳轉(zhuǎn)
舱殿、異常跳轉(zhuǎn)
等,所以具有這些功能的指令才會產(chǎn)生Safepoint险掀。
?對于Sefepoint沪袭,另一個需要考慮的問題是如何在GC發(fā)生時讓所有線程(這里不包括執(zhí)行JNI調(diào)用的線程)都“跑”到最近的安全點上再停頓下來
。這里有兩種方案可供選擇:
-
搶先式中斷(Preemptive Suspension)
搶先式中斷不需要線程的執(zhí)行代碼主動去配合迷郑,在GC發(fā)生時枝恋,首先把所有線程全部中斷,如果發(fā)現(xiàn)有線程中斷的地方不在安全點上嗡害,就恢復線程焚碌,讓它“跑”到安全點上。
現(xiàn)在幾乎沒有虛擬機實現(xiàn)采用搶先式中斷來暫停線程從而響應GC事件霸妹。 -
主動式中斷(Voluntary Suspension)
: 主動式中斷的思想是當GC需要中斷線程的時候十电,不直接對線程操作,僅僅簡單地設(shè)置一個標志
叹螟,各個線程執(zhí)行時主動去輪詢這個標志鹃骂,發(fā)現(xiàn)中斷標志為真時就自己中斷掛起。輪詢標志的地方和安全點是重合的**罢绽,另外**再加上創(chuàng)建對象需要分配內(nèi)存的地方
分俯。
4.5 安全區(qū)域(Safe Region)
?Safepoint
機制保證了程序執(zhí)行時
寓调,在不太長的時間內(nèi)就會遇到可進入GC的Safepoint蔼啦。但是,程序“不執(zhí)行”的時候(如線程處于Sleep狀態(tài)或Blocked狀態(tài))
蒿叠,這時線程無法響應JVM的中斷請求,“走到”安全的地方去中斷掛起蚣常,這時候就需要安全區(qū)域(Safe Region)
來解決市咽。
?安全區(qū)域
是指在一段代碼片段之中,引用關(guān)系不會發(fā)生變化抵蚊。在這個區(qū)域中的任意地方開始GC都是安全的施绎。
我們也可以把Safe Region看做是被擴展了的Safepoint。
?在線程執(zhí)行到Safe Region中的代碼時贞绳,首先標識自己已經(jīng)進入了Safe Region
谷醉,那樣,當在這段時間里JVM要發(fā)起GC時熔酷,就不用管標識自己為Safe Region狀態(tài)的線程了孤紧。在線程要離開Safe Region時,它要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點枚舉(或者是整個GC過程)
拒秘,如果完成了,那線程就繼續(xù)執(zhí)行臭猜,否則它就必須等待直到收到可以安全離開Safe Region的信號為止躺酒。
五、內(nèi)存分配策略
?Java的自動內(nèi)存管理最終可以歸結(jié)為自動化地解決了兩個問題:
- 給對象分配內(nèi)存
- 回收分配給對象的內(nèi)存
?對象的內(nèi)存分配通常是在堆上分配(除此以外還有可能經(jīng)過JIT編譯后被拆散為標量類型并間接地棧上分配)蔑歌,對象主要分配在新生代的Eden區(qū)上羹应,如果啟動了本地線程分配緩沖,將按線程優(yōu)先在TLAB上分配次屠。少數(shù)情況下也可能會直接分配在老年代中园匹,分配的規(guī)則并不是固定的,實際取決于垃圾收集器的具體組合以及虛擬機中與內(nèi)存相關(guān)的參數(shù)的設(shè)置劫灶。至于內(nèi)存回收策略裸违,在上文已經(jīng)描述得很詳盡了。
?下面以使用Serial/Serial Old收集器(將在下一篇文章中講解)為例本昏,介紹內(nèi)存分配的策略供汛。
5.1 對象優(yōu)先在Eden區(qū)分配
?大多數(shù)情況下,對象在新生代的Eden區(qū)中分配涌穆。當Eden區(qū)沒有足夠空間進行分配時怔昨,虛擬機將發(fā)起一次Minor GC。
5.2 大對象直接進入老年代
?所謂的大對象是指宿稀,需要大量連續(xù)內(nèi)存空間的Java對象趁舀,最典型的大對象就是很長的字符串以及數(shù)組。大對象對虛擬機的內(nèi)存分配來說是一個壞消息(尤其是遇到朝生夕滅的“短命大對象”祝沸,寫程序時應避免)矮烹,經(jīng)常出現(xiàn)大對象容易導致內(nèi)存還有不少空間時就提前觸發(fā)GC以獲取足夠的連續(xù)空間來安置它們
巡蘸。
?虛擬機提供了一個-XX:PretenureSizeThreshold
參數(shù),令大于這個設(shè)置值的對象直接在老年代分配擂送。這樣做的目的是避免在Eden區(qū)及兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復制
(新生代采用復制算法回收內(nèi)存)悦荒。
5.2 長期存活的對象將進入老年代
?既然虛擬機采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須能識別哪些對象應放在新生代嘹吨,哪些對象應放在老年代中搬味。為了做到這點,虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器
蟀拷。如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活碰纬,并且能被Survivor容納的話,將被移動到Survivor空間中问芬,并且對象年齡設(shè)為1悦析。對象在Survivor區(qū)中每“熬過”一次Minor GC,年齡就增加1歲此衅,當它的年齡增加到一定程度(默認為15歲)强戴,就將會被晉升到老年代中。
對象晉升老年代的年齡閾值挡鞍,可以通過參數(shù)-XX:MaxTenuringThreshold
設(shè)置骑歹。
5.3 動態(tài)對象年齡判定
?為了能更好地適應不同程序的內(nèi)存狀況,虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold
才能晉升老年代墨微,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半道媚,年齡大于或等于該年齡的對象就可以直接進入老年代
,無須等到MaxTenuringThreshold
中要求的年齡翘县。
5.4 空間分配擔保
?在發(fā)生Minor GC之前
最域,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立锈麸,那么Minor GC可以確保是安全的镀脂。如果不成立,則虛擬機會查看HandlePromotionFailure
設(shè)置值是否允許擔保失敗掐隐。如果允許狗热,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于虑省,將嘗試著進行一次Minor GC匿刮,盡管這次Minor GC是有風險的;如果小于探颈,或者HandlePromotionFailure
設(shè)置不允許冒險熟丸,那這時也要改為進行一次Full GC
。
?前面提到過伪节,新生代使用復制收集算法光羞,但為了內(nèi)存利用率绩鸣,只使用其中一個Survivor空間來作為輪換備份,因此當出現(xiàn)大量對象在Minor GC后仍然存活的情況(最極端的情況就是內(nèi)存回收后新生代中所有對象都存活)纱兑,就需要老年代進行分配擔保呀闻,把Survivor無法容納的對象直接進入老年代。
與生活中的貸款擔保類似潜慎,老年代要進行這樣的擔保捡多,前提是老年代本身還有容納這些對象的剩余空間,一共有多少對象會活下來在實際完成內(nèi)存回收之前是無法明確知道的铐炫,所以只好取之前每一次回收晉升到老年代對象容量的平均大小值作為經(jīng)驗值垒手,與老年代的剩余空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間倒信。
?取平均值進行比較其實仍然是一種動態(tài)概率的手段科贬,也就是說,如果某次Minor GC存活后的對象突增鳖悠,遠遠高于平均值的話榜掌,依然會導致擔保失敗(Handle Promotion Failure)
竞穷。如果出現(xiàn)了HandlePromotionFailure
失敗唐责,那就只好在失敗后重新發(fā)起一次Full GC。雖然擔保失敗時繞的圈子是最大的瘾带,但大部分情況下都還是會將HandlePromotionFailure
開關(guān)打開,避免Full GC過于頻繁熟菲。
六看政、 Full GC的觸發(fā)條件
?對于Minor GC,其觸發(fā)條件非常簡單抄罕,當Eden區(qū)空間滿時允蚣,就將觸發(fā)一次Minor GC。而Full GC則相對復雜呆贿,因此本節(jié)我們主要介紹Full GC的觸發(fā)條件嚷兔。
6.1 調(diào)用System.gc()
?此方法的調(diào)用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發(fā) Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數(shù)。因此強烈建議能不使用此方法就不要使用做入,讓虛擬機自己去管理它的內(nèi)存冒晰,可通過-XX:+ DisableExplicitGC
來禁止RMI調(diào)用System.gc()。
6.2 老年代空間不足
?老年代空間不足的常見場景為前文所講的大對象直接進入老年代
竟块、長期存活的對象進入老年代
等壶运,當執(zhí)行Full GC后空間仍然不足,則拋出如下錯誤: Java.lang.OutOfMemoryError: Java heap space
為避免以上兩種狀況引起的Full GC浪秘,調(diào)優(yōu)時應盡量做到讓對象在Minor GC階段被回收蒋情、讓對象在新生代多存活一段時間及不要創(chuàng)建過大的對象及數(shù)組埠况。
6.3 空間分配擔保失敗
?前文介紹過,使用復制算法的Minor GC需要老年代的內(nèi)存空間作擔保棵癣,如果出現(xiàn)了HandlePromotionFailure
擔保失敗辕翰,則會觸發(fā)Full GC。
6.4 JDK 1.7及以前的永久代空間不足
?在JDK 1.7及以前狈谊,HotSpot虛擬機中的方法區(qū)是用永久代實現(xiàn)的喜命,永久代中存放的為一些class的信息、常量的畴、靜態(tài)變量等數(shù)據(jù)渊抄,當系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時丧裁,Permanet Generation可能會被占滿护桦,在未配置為采用CMS GC的情況下也會執(zhí)行Full GC。如果經(jīng)過Full GC仍然回收不了煎娇,那么JVM會拋出如下錯誤信息: java.lang.OutOfMemoryError: PermGen space
為避免PermGen占滿造成Full GC現(xiàn)象二庵,可采用的方法為增大PermGen空間或轉(zhuǎn)為使用CMS GC。
?在JDK 1.8中用元空間替換了永久代作為方法區(qū)的實現(xiàn)缓呛,元空間是本地內(nèi)存催享,因此減少了一種Full GC觸發(fā)的可能性。
6.5 Concurrent Mode Failure
?執(zhí)行CMS GC的過程中同時有對象要放入老年代哟绊,而此時老年代空間不足(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發(fā)Full GC)因妙,便會報Concurrent Mode Failure
錯誤,并觸發(fā)Full GC票髓。
小結(jié)
?本文簡要地介紹了HotSpot虛擬機如何去發(fā)起內(nèi)存回收的問題攀涵,也解答了文章開頭提出的三個問題中的前兩個——“哪些內(nèi)存需要回收”和“何時回收”,同時對于第三個問題——“如何回收”洽沟,在原理層面作出了解答以故。
參考
- 《深入理解Java虛擬機——JVM高級特性與最佳實踐》-周志明