JVM內(nèi)存的分配與回收大致可分為如下4個(gè)步驟: 何時(shí)分配 -> 怎樣分配 -> 何時(shí)回收 -> 怎樣回收。
除了在概念上可簡單認(rèn)為new時(shí)分配外, 我們著重介紹后面的3個(gè)步驟:
I. 怎樣分配- JVM內(nèi)存分配策略
對象內(nèi)存主要分配在新生代Eden區(qū), 如果啟用了本地線程分配緩沖, 則優(yōu)先在TLAB上分配, 少數(shù)情況能會直接分配在老年代, 或被拆分成標(biāo)量類型在棧上分配(JIT優(yōu)化). 分配的規(guī)則并不是百分百固定, 細(xì)節(jié)主要取決于垃圾收集器組合, 以及VM內(nèi)存相關(guān)的參數(shù).
對象分配
優(yōu)先在Eden區(qū)分配
在JVM內(nèi)存模型一文中, 我們大致了解了VM年輕代堆內(nèi)存可以劃分為一塊Eden區(qū)和兩塊Survivor區(qū). 在大多數(shù)情況下, 對象在新生代Eden區(qū)中分配, 當(dāng)Eden區(qū)沒有足夠空間分配時(shí), VM發(fā)起一次Minor GC, 將Eden區(qū)和其中一塊Survivor區(qū)內(nèi)尚存活的對象放入另一塊Survivor區(qū)域, 如果在Minor GC期間發(fā)現(xiàn)新生代存活對象無法放入空閑的Survivor區(qū), 則會通過空間分配擔(dān)保機(jī)制使對象提前進(jìn)入老年代(空間分配擔(dān)保見下).
大對象直接進(jìn)入老年代
Serial和ParNew兩款收集器提供了-XX:PretenureSizeThreshold的參數(shù), 令大于該值的大對象直接在老年代分配, 這樣做的目的是避免在Eden區(qū)和Survivor區(qū)之間產(chǎn)生大量的內(nèi)存復(fù)制(大對象一般指 需要大量連續(xù)內(nèi)存的Java對象, 如很長的字符串和數(shù)組), 因此大對象容易導(dǎo)致還有不少空閑內(nèi)存就提前觸發(fā)GC以獲取足夠的連續(xù)空間.
對象晉升
年齡閾值
VM為每個(gè)對象定義了一個(gè)對象年齡(Age)計(jì)數(shù)器, 對象在Eden出生如果經(jīng)第一次Minor GC后仍然存活, 且能被Survivor容納的話, 將被移動到Survivor空間中, 并將年齡設(shè)為1. 以后對象在Survivor區(qū)中每熬過一次Minor GC年齡就+1. 當(dāng)增加到一定程度(-XX:MaxTenuringThreshold, 默認(rèn)15), 將會晉升到老年代.
提前晉升: 動態(tài)年齡判定
然而VM并不總是要求對象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代: 如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半, 年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代, 而無須等到晉升年齡.
II. 何時(shí)回收-對象生死判定
(哪些內(nèi)存需要回收/何時(shí)回收)
在堆里面存放著Java世界中幾乎所有的對象實(shí)例, 垃圾收集器在對堆進(jìn)行回收前, 第一件事就是判斷哪些對象已死(可回收).
可達(dá)性分析算法
在主流商用語言(如Java、C#)的主流實(shí)現(xiàn)中, 都是通過可達(dá)性分析算法來判定對象是否存活的: 通過一系列的稱為 GC Roots 的對象作為起點(diǎn), 然后向下搜索; 搜索所走過的路徑稱為引用鏈/Reference Chain, 當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí), 即該對象不可達(dá), 也就說明此對象是不可用的, 如下圖: Object5、6、7 雖然互有關(guān)聯(lián), 但它們到GC Roots是不可達(dá)的, 因此也會被判定為可回收的對象:
在Java, 可作為GC Roots的對象包括:
方法區(qū): 類靜態(tài)屬性引用的對象;
方法區(qū): 常量引用的對象;
虛擬機(jī)棧(本地變量表)中引用的對象;
本地方法棧JNI(Native方法)中引用的對象。
注: 即使在可達(dá)性分析算法中不可達(dá)的對象, VM也并不是馬上對其回收, 因?yàn)橐嬲嬉粋€(gè)對象死亡, 至少要經(jīng)歷兩次標(biāo)記過程: 第一次是在可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈, 第二次是GC對在F-Queue執(zhí)行隊(duì)列中的對象進(jìn)行的小規(guī)模標(biāo)記(對象需要覆蓋finalize()方法且沒被調(diào)用過).
III. GC原理- 垃圾收集算法
分代收集算法 VS 分區(qū)收集算法
分代收集
當(dāng)前主流VM垃圾收集都采用”分代收集”(Generational Collection)算法, 這種算法會根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊, 如JVM中的 新生代、老年代、永久代. 這樣就可以根據(jù)各年代特點(diǎn)分別采用最適當(dāng)?shù)腉C算法:
在新生代: 每次垃圾收集都能發(fā)現(xiàn)大批對象已死, 只有少量存活. 因此選用復(fù)制算法, 只需要付出少量存活對象的復(fù)制成本就可以完成收集.
在老年代: 因?yàn)閷ο蟠婊盥矢?、沒有額外空間對它進(jìn)行分配擔(dān)保, 就必須采用“標(biāo)記—清理”或“標(biāo)記—整理”算法來進(jìn)行回收, 不必進(jìn)行內(nèi)存復(fù)制, 且直接騰出空閑內(nèi)存.
分區(qū)收集
上面介紹的分代收集算法是將對象的生命周期按長短劃分為兩個(gè)部分, 而分區(qū)算法則將整個(gè)堆空間劃分為連續(xù)的不同小區(qū)間, 每個(gè)小區(qū)間獨(dú)立使用, 獨(dú)立回收. 這樣做的好處是可以控制一次回收多少個(gè)小區(qū)間.
在相同條件下, 堆空間越大, 一次GC耗時(shí)就越長, 從而產(chǎn)生的停頓也越長. 為了更好地控制GC產(chǎn)生的停頓時(shí)間, 將一塊大的內(nèi)存區(qū)域分割為多個(gè)小塊, 根據(jù)目標(biāo)停頓時(shí)間, 每次合理地回收若干個(gè)小區(qū)間(而不是整個(gè)堆), 從而減少一次GC所產(chǎn)生的停頓.
分代收集
新生代-復(fù)制算法
該算法的核心是將可用內(nèi)存按容量劃分為大小相等的兩塊, 每次只用其中一塊, 當(dāng)這一塊的內(nèi)存用完, 就將還存活的對象復(fù)制到另外一塊上面, 然后把已使用過的內(nèi)存空間一次清理掉.
這使得每次只對其中一塊內(nèi)存進(jìn)行回收, 分配也就不用考慮內(nèi)存碎片等復(fù)雜情況, 實(shí)現(xiàn)簡單且運(yùn)行高效.
現(xiàn)代商用VM的新生代均采用復(fù)制算法, 但由于新生代中的98%的對象都是生存周期極短的, 因此并不需完全按照1∶1的比例劃分新生代空間, 而是將新生代劃分為一塊較大的Eden區(qū)和兩塊較小的Survivor區(qū)(HotSpot默認(rèn)Eden和Survivor的大小比例為8∶1), 每次只用Eden和其中一塊Survivor. 當(dāng)發(fā)生MinorGC時(shí), 將Eden和Survivor中還存活著的對象一次性地拷貝到另外一塊Survivor上, 最后清理掉Eden和剛才用過的Survivor的空間. 當(dāng)Survivor空間不夠用(不足以保存尚存活的對象)時(shí), 需要依賴?yán)夏甏M(jìn)行空間分配擔(dān)保機(jī)制, 這部分內(nèi)存直接進(jìn)入老年代.
老年代-標(biāo)記清除算法
該算法分為“標(biāo)記”和“清除”兩個(gè)階段: 首先標(biāo)記出所有需要回收的對象(可達(dá)性分析), 在標(biāo)記完成后統(tǒng)一清理掉所有被標(biāo)記的對象.
該算法會有以下兩個(gè)問題:
效率問題: 標(biāo)記和清除過程的效率都不高;
空間問題: 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片, 空間碎片太多可能會導(dǎo)致在運(yùn)行過程中需要分配較大對象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集.
老年代-標(biāo)記整理算法
標(biāo)記清除算法會產(chǎn)生內(nèi)存碎片問題, 而復(fù)制算法需要有額外的內(nèi)存擔(dān)保空間, 于是針對老年代的特點(diǎn), 又有了標(biāo)記整理算法. 標(biāo)記整理算法的標(biāo)記過程與標(biāo)記清除算法相同, 但后續(xù)步驟不再對可回收對象直接清理, 而是讓所有存活的對象都向一端移動,然后清理掉端邊界以外的內(nèi)存.
永久代-方法區(qū)回收
在方法區(qū)進(jìn)行垃圾回收一般”性價(jià)比”較低, 因?yàn)樵诜椒▍^(qū)主要回收兩部分內(nèi)容:廢棄常量和無用的類. 回收廢棄常量與回收其他年代中的對象類似, 但要判斷一個(gè)類是否無用則條件相當(dāng)苛刻:
該類所有的實(shí)例都已經(jīng)被回收, Java堆中不存在該類的任何實(shí)例;
該類對應(yīng)的Class對象沒有在任何地方被引用(也就是在任何地方都無法通過反射訪問該類的方法);
加載該類的ClassLoader已經(jīng)被回收.
但即使?jié)M足以上條件也未必一定會回收, Hotspot VM還提供了-Xnoclassgc參數(shù)控制(關(guān)閉CLASS的垃圾回收功能). 因此在大量使用動態(tài)代理、CGLib等字節(jié)碼框架的應(yīng)用中一定要關(guān)閉該選項(xiàng), 開啟VM的類卸載功能, 以保證方法區(qū)不會溢出.
補(bǔ)充: 空間分配擔(dān)保
在執(zhí)行Minor GC前, VM會首先檢查老年代是否有足夠的空間存放新生代尚存活對象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個(gè)Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況時(shí), 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無法容納的對象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對象. 但存活對象的大小在實(shí)際完成GC前是無法明確知道的, 因此Minor GC前, VM會先首先檢查老年代連續(xù)空間是否大于新生代對象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間).
然而取歷次晉升的對象的平均大小也是有一定風(fēng)險(xiǎn)的, 如果某次Minor GC存活后的對象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無法存放這些對象了), 此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).
IX. GC實(shí)現(xiàn)- 垃圾收集器
GC實(shí)現(xiàn)目標(biāo): 準(zhǔn)確、高效、低停頓、空閑內(nèi)存規(guī)整.
新生代
1. Serial收集器
Serial收集器是Hotspot運(yùn)行在Client模式下的默認(rèn)新生代收集器, 它的特點(diǎn)是 只用一個(gè)CPU/一條收集線程去完成GC工作, 且在進(jìn)行垃圾收集時(shí)必須暫停其他所有的工作線程(“Stop The World” -后面簡稱STW).
雖然是單線程收集, 但它卻簡單而高效, 在VM管理內(nèi)存不大的情況下(收集幾十M~一兩百M(fèi)的新生代), 停頓時(shí)間完全可以控制在幾十毫秒~一百多毫秒內(nèi).
2. ParNew收集器
ParNew收集器其實(shí)是前面Serial的多線程版本, 除使用多條線程進(jìn)行GC外, 包括Serial可用的所有控制參數(shù)、收集算法、STW、對象分配規(guī)則、回收策略等都與Serial完全一樣(也是VM啟用CMS收集器-XX: +UseConcMarkSweepGC的默認(rèn)新生代收集器).
由于存在線程切換的開銷, ParNew在單CPU的環(huán)境中比不上Serial, 且在通過超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中也不能100%保證能超越Serial. 但隨著可用的CPU數(shù)量的增加, 收集效率肯定也會大大增加(ParNew收集線程數(shù)與CPU的數(shù)量相同, 因此在CPU數(shù)量過大的環(huán)境中, 可用-XX:ParallelGCThreads參數(shù)控制GC線程數(shù)).
3. Parallel Scavenge收集器
與ParNew類似, Parallel Scavenge也是使用復(fù)制算法, 也是并行多線程收集器. 但與其他收集器關(guān)注盡可能縮短垃圾收集時(shí)間不同, Parallel Scavenge更關(guān)注系統(tǒng)吞吐量:
系統(tǒng)吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間)
停頓時(shí)間越短就越適用于用戶交互的程序-良好的響應(yīng)速度能提升用戶的體驗(yàn);而高吞吐量則適用于后臺運(yùn)算而不需要太多交互的任務(wù)-可以最高效率地利用CPU時(shí)間,盡快地完成程序的運(yùn)算任務(wù). Parallel Scavenge提供了如下參數(shù)設(shè)置系統(tǒng)吞吐量:
老年代
Serial Old收集器
Serial Old是Serial收集器的老年代版本, 同樣是單線程收集器,使用“標(biāo)記-整理”算法:
Serial Old是Serial收集器的老年代版本, 同樣是單線程收集器,使用“標(biāo)記-整理”算法:
Serial Old應(yīng)用場景如下:
JDK 1.5之前與Parallel Scavenge收集器搭配使用;
作為CMS收集器的后備預(yù)案, 在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)啟用(見下:CMS收集器).
Parallel Old收集器
Parallel Old是Parallel Scavenge收老年代版本, 使用多線程和“標(biāo)記-整理”算法, 吞吐量優(yōu)先, 主要與Parallel Scavenge配合在 注重吞吐量 及 CPU資源敏感 系統(tǒng)內(nèi)使用:
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一款具有劃時(shí)代意義的收集器, 一款真正意義上的并發(fā)收集器, 雖然現(xiàn)在已經(jīng)有了理論意義上表現(xiàn)更好的G1收集器, 但現(xiàn)在主流互聯(lián)網(wǎng)企業(yè)線上選用的仍是CMS(如Taobao、微店).
CMS是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器(CMS又稱多并發(fā)低暫停的收集器), 基于”標(biāo)記-清除”算法實(shí)現(xiàn), 整個(gè)GC過程分為以下4個(gè)步驟:
1. 初始標(biāo)記(CMS initial mark)
2. 并發(fā)標(biāo)記(CMS concurrent mark: GC Roots Tracing過程)
3. 重新標(biāo)記(CMS remark)
4. 并發(fā)清除(CMS concurrent sweep: 已死象將會就地釋放, 注意: 此處沒有壓縮)
其中兩個(gè)加粗的步驟(初始標(biāo)記、重新標(biāo)記)仍需STW. 但初始標(biāo)記僅只標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象, 速度很快; 而重新標(biāo)記則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄, 雖然一般比初始標(biāo)記階段稍長, 但要遠(yuǎn)小于并發(fā)標(biāo)記時(shí)間.
(由于整個(gè)GC過程耗時(shí)最長的并發(fā)標(biāo)記和并發(fā)清除階段的GC線程可與用戶線程一起工作, 所以總體上CMS的GC過程是與用戶線程一起并發(fā)地執(zhí)行的.
由于CMS收集器將整個(gè)GC過程進(jìn)行了更細(xì)粒度的劃分, 因此可以實(shí)現(xiàn)并發(fā)收集、低停頓的優(yōu)勢, 但它也并非十分完美, 其存在缺點(diǎn)及解決策略如下:
1. CMS默認(rèn)啟動的回收線程數(shù)=(CPU數(shù)目+3)4
當(dāng)CPU數(shù)>4時(shí), GC線程最多占用不超過25%的CPU資源, 但是當(dāng)CPU數(shù)<=4時(shí), GC線程可能就會過多的占用用戶CPU資源, 從而導(dǎo)致應(yīng)用程序變慢, 總吞吐量降低.
2. 無法處理浮動垃圾, 可能出現(xiàn)Promotion Failure、Concurrent Mode Failure而導(dǎo)致另一次Full GC的產(chǎn)生: 浮動垃圾是指在CMS并發(fā)清理階段用戶線程運(yùn)行而產(chǎn)生的新垃圾. 由于在GC階段用戶線程還需運(yùn)行, 因此還需要預(yù)留足夠的內(nèi)存空間給用戶線程使用, 導(dǎo)致CMS不能像其他收集器那樣等到老年代幾乎填滿了再進(jìn)行收集. 因此CMS提供了-XX:CMSInitiatingOccupancyFraction參數(shù)來設(shè)置GC的觸發(fā)百分比(以及-XX:+UseCMSInitiatingOccupancyOnly來啟用該觸發(fā)百分比), 當(dāng)老年代的使用空間超過該比例后CMS就會被觸發(fā)(JDK 1.6之后默認(rèn)92%). 但當(dāng)CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要, 就會出現(xiàn)上述Promotion Failure等失敗, 這時(shí)VM將啟動后備預(yù)案: 臨時(shí)啟用Serial Old收集器來重新執(zhí)行Full GC(CMS通常配合大內(nèi)存使用, 一旦大內(nèi)存轉(zhuǎn)入串行的Serial GC, 那停頓的時(shí)間就是大家都不愿看到的了).
3. 最后, 由于CMS采用”標(biāo)記-清除”算法實(shí)現(xiàn), 可能會產(chǎn)生大量內(nèi)存碎片. 內(nèi)存碎片過多可能會導(dǎo)致無法分配大對象而提前觸發(fā)Full GC. 因此CMS提供了-XX:+UseCMSCompactAtFullCollection開關(guān)參數(shù), 用于在Full GC后再執(zhí)行一個(gè)碎片整理過程. 但內(nèi)存整理是無法并發(fā)的, 內(nèi)存碎片問題雖然沒有了, 但停頓時(shí)間也因此變長了, 因此CMS還提供了另外一個(gè)參數(shù)-XX:CMSFullGCsBeforeCompaction用于設(shè)置在執(zhí)行N次不進(jìn)行內(nèi)存整理的Full GC后, 跟著來一次帶整理的(默認(rèn)為0: 每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理).
分區(qū)收集- G1收集器
G1(Garbage-First)是一款面向服務(wù)端應(yīng)用的收集器, 主要目標(biāo)用于配備多顆CPU的服務(wù)器治理大內(nèi)存.
- G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).
- -XX:+UseG1GC 啟用G1收集器.
與其他基于分代的收集器不同, G1將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region), 雖然還保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔離的了, 它們都是一部分Region(不需要連續(xù))的集合.
每塊區(qū)域既有可能屬于O區(qū)、也有可能是Y區(qū), 因此不需要一次就對整個(gè)老年代/新生代回收. 而是當(dāng)線程并發(fā)尋找可回收的對象時(shí), 有些區(qū)塊包含可回收的對象要比其他區(qū)塊多很多. 雖然在清理這些區(qū)塊時(shí)G1仍然需要暫停應(yīng)用線程, 但可以用相對較少的時(shí)間優(yōu)先回收垃圾較多的Region(這也是G1命名的來源). 這種方式保證了G1可以在有限的時(shí)間內(nèi)獲取盡可能高的收集效率.
新生代收集
G1的新生代收集跟ParNew類似: 存活的對象被轉(zhuǎn)移到一個(gè)/多個(gè)Survivor Regions. 如果存活時(shí)間達(dá)到閥值, 這部分對象就會被提升到老年代.
G1的新生代收集特點(diǎn)如下:
一整塊堆內(nèi)存被分為多個(gè)Regions.
存活對象被拷貝到新的Survivor區(qū)或老年代.
年輕代內(nèi)存由一組不連續(xù)的heap區(qū)組成, 這種方法使得可以動態(tài)調(diào)整各代區(qū)域尺寸.
Young GCs會有STW事件, 進(jìn)行時(shí)所有應(yīng)用程序線程都會被暫停.
多線程并發(fā)GC.
老年代收集
G1老年代GC會執(zhí)行以下階段:
注: 一下有些階段也是年輕代垃圾收集的一部分.
詳細(xì)步驟可參考 Oracle官方文檔-The G1 Garbage Collector Step by Step.
G1老年代GC特點(diǎn)如下:
并發(fā)標(biāo)記階段(index 3)
在與應(yīng)用程序并發(fā)執(zhí)行的過程中會計(jì)算活躍度信息.
這些活躍度信息標(biāo)識出那些regions最適合在STW期間回收(which regions will be best to reclaim during an evacuation pause).
不像CMS有清理階段.
再次標(biāo)記階段(index 4)
使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多.
空region直接被回收.
拷貝/清理階段(Copying/Cleanup Phase)
年輕代與老年代同時(shí)回收.
老年代內(nèi)存回收會基于他的活躍度信息.
補(bǔ)充: 關(guān)于Remembered Set
G1收集器中, Region之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用都是使用Remembered Set來避免掃描全堆. G1中每個(gè)Region都有一個(gè)與之對應(yīng)的Remembered Set, VM發(fā)現(xiàn)程序?qū)eference類型數(shù)據(jù)進(jìn)行寫操作時(shí), 會產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫操作, 檢查Reference引用的對象是否處于不同的Region中(在分代例子中就是檢查是否老年代中的對象引用了新生代的對象), 如果是, 便通過CardTable把相關(guān)引用信息記錄到被引用對象所屬的Region的Remembered Set中. 當(dāng)內(nèi)存回收時(shí), 在GC根節(jié)點(diǎn)的枚舉范圍加入Remembered Set即可保證不對全局堆掃描也不會有遺漏.
V. JVM小工具
在${JAVA_HOME}/bin/目錄下Sun/Oracle給我們提供了一些處理應(yīng)用程序性能問題、定位故障的工具, 包含
VI. VM常用參數(shù)整理
在此處無法列舉所有的參數(shù)以及他們的應(yīng)用場景, 詳細(xì)移步Oracle官方文檔-Java HotSpot VM Options.
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
參考 & 擴(kuò)展
深入理解Java虛擬機(jī)
JVM內(nèi)幕:Java虛擬機(jī)詳解 (力薦)
G1垃圾收集器入門
Getting Started with the G1 Garbage Collector
深入理解G1垃圾收集器
解析JDK 7的Garbage-First收集器
The Garbage-First Garbage Collector
Memory Management in the Java HotSpot Virtual Machine
Java HotSpot VM Options
JVM實(shí)用參數(shù)(一)JVM類型以及編譯器模式
JVM內(nèi)存回收理論與實(shí)現(xiàn)
基于OpenJDK深度定制的淘寶JVM(TaobaoVM)
轉(zhuǎn)載于:http://mp.weixin.qq.com/s/rNFdMUTlgl7s3AFYc3Oy6A