0. JVM內(nèi)存組成
JVM內(nèi)存主要由兩部分組成:a.線程私有內(nèi)存區(qū)域称勋;b.線程公共內(nèi)存區(qū)域嵌赠。
線程公用的內(nèi)存區(qū)域主要包括:
- 堆
- 方法區(qū)
線程私有的內(nèi)存區(qū)域主要包括:
- jvm棧
- 程序計(jì)數(shù)器
- 本地方法棧
JVM GC主要就是針對(duì)線程公共的內(nèi)存區(qū)域進(jìn)行的坷衍。
1. GC 概念描述
GC,即 garbage collection 是進(jìn)程查看堆內(nèi)存,分辨哪些對(duì)象還在被使用中丢间,哪些對(duì)象已經(jīng)不再使用,并刪除不再使用的對(duì)象的過(guò)程驹针『娲欤“被使用” 指程序中的某些部分任然持有對(duì)該對(duì)象的引用,而 “不再被使用” 就是指沒(méi)有任何部分持有對(duì)該對(duì)象的引用柬甥,對(duì)于 “不再被使用” 的對(duì)象饮六,其消耗的內(nèi)存可以被回收重用。
與C和C++不同苛蒲,java的內(nèi)存回收不再全權(quán)由程序員否則卤橄,而是交給系統(tǒng)自動(dòng)進(jìn)行。java中的GC可以分為下面這些步驟:
步驟 1 標(biāo)記
回收的第一步就是標(biāo)記出哪些對(duì)象已經(jīng)不再使用了臂外。當(dāng)對(duì)象很多時(shí)這一步需要消耗大量時(shí)間窟扑。
步驟 2a 刪除
標(biāo)記出哪些對(duì)象不再使用后,下一步自然是將其刪除了漏健。但是從上圖可以看出嚎货,這個(gè)方法有一個(gè)問(wèn)題:在內(nèi)存中形成了不連續(xù)的片段,這樣可能無(wú)法分配一大塊空間給某個(gè)大對(duì)象蔫浆。
步驟 2b 刪除并整理
英文原文叫 Compacting 殖属,個(gè)人認(rèn)為翻譯為壓縮更符合原文的意思。
在刪除無(wú)用對(duì)象后瓦盛,將還在使用中的內(nèi)存移動(dòng)到一個(gè)連續(xù)的空間內(nèi)洗显,這樣消除了 “內(nèi)存碎片” 的影響。
步驟 2c 刪除并復(fù)制
這種方式使用兩個(gè)內(nèi)存區(qū)域原环,一個(gè)內(nèi)存區(qū)域中需要進(jìn)行GC后挠唆,就將該內(nèi)存區(qū)域中仍然被引用的對(duì)象移動(dòng)到另外一個(gè)區(qū)域中,不會(huì)產(chǎn)生內(nèi)存碎片嘱吗,速度也很快玄组,然而缺點(diǎn)是需要使用額外的內(nèi)存。
分代回收
上圖描述了對(duì)象生命周期柜与,可以看出絕大多數(shù)對(duì)象都是 “朝生暮死”巧勤,根據(jù)對(duì)象不同的特性,可以將對(duì)象分為幾類弄匕,分別使用不同的方法進(jìn)行GC颅悉,減少GC消耗的時(shí)間。
2. HotSpot 的分代回收
分代回收說(shuō)明
上圖是 JAVA 8 的 HotSpot的分代策略迁匠。注意在 JAVA 8 中 PermGen 被MetaSpace取代了剩瓶,具體可以參見(jiàn)這篇文章驹溃。eden+S0+S1合起來(lái)稱為 Young Generation,S0+S1 稱為Survivor Space延曙,而 Tenured 則是 Old Generation豌鹤。
對(duì)象首先被分配到Eden區(qū)域,程序剛開(kāi)始運(yùn)行時(shí) from 和 to 都是空的枝缔。
隨后布疙,Eden區(qū)域被填滿,于是觸發(fā) minor GC愿卸,將有效對(duì)象全部移動(dòng)到 S0 或 S1 中去灵临。
隨著一次次的 minor GC,存活的對(duì)象在S0和S1之間移動(dòng)來(lái)移動(dòng)去趴荸,將會(huì)在其對(duì)象頭部記錄經(jīng)歷過(guò)的 GC 次數(shù)儒溉。
當(dāng)對(duì)象經(jīng)歷的GC次數(shù)大于某個(gè)值后,將會(huì)被認(rèn)為是一個(gè)有較長(zhǎng)生命周期的對(duì)象发钝,從而將其移動(dòng)到老年代顿涣。
使用VisualVm,加上GC-plugin可以看到垃圾收集的過(guò)程酝豪,以我電腦上運(yùn)行的IDEA為例涛碑,可以看到survivor0和survivor1交替變?yōu)榭眨?Old Gen 和 metaspace 在很長(zhǎng)一段時(shí)間內(nèi)幾乎保持不變寓调。
與GC相關(guān)的部分JVM參數(shù)
Switch | Description |
---|---|
-Xms | jvm堆的初始大小. |
-Xmx | jvm堆的最大大小. |
-Xmn | 年輕代的大小. |
-XX:PermSize | Java1.8之前的永久代大小 |
-XX:MaxPermSize | Java1.8之前的永久代最大大小 |
-XX:MetaspaceSize | MetaspaceSize大小 |
-XX:MaxMetaspaceSize | MetaspaceSize最大大小 |
Serial GC算法
Serial GC 是一種單線程執(zhí)行的 標(biāo)記-整理 GC算法锌唾,適用于對(duì)GC延遲不敏感的客戶端java程序锄码。另外夺英,如果很多個(gè)jvm運(yùn)行在同一臺(tái)機(jī)器上時(shí),也適合使用這種算法滋捶。
該算法使用如下參數(shù)打開(kāi):
-XX:+UseSerialGC
一個(gè)運(yùn)行實(shí)例:
java -Xmx12m -Xms3m -Xmn1m XX:MetaspaceSize XX:MaxMetaspaceSize=20m
-XX:+UseSerialGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Parallel GC
parallel GC 使用多線程進(jìn)行年輕代收集痛悯,是Serial的多線程版本,當(dāng)主機(jī)有N個(gè) CPU 時(shí)重窟,默認(rèn)會(huì)使用N個(gè)垃圾線程载萌。使用命令行選項(xiàng)可以控制垃圾收集的線程數(shù)目:
-XX:ParallelGCThreads=<線程數(shù)目>
注意,在單核主機(jī)上巡扇,是不會(huì)使用Parallel GC的扭仁,在2核主機(jī)上,Parallel GC 可能和默認(rèn)的 GC 收集器工作效率相同厅翔,而在大于2核的主機(jī)上乖坠,Parallel GC 一般會(huì)有更好的表現(xiàn)。當(dāng)然刀闷,一切還是需要以實(shí)際測(cè)量為準(zhǔn)熊泵。
Parallel GC適合于需要高吞吐量而對(duì)暫停時(shí)間不敏感的場(chǎng)合仰迁,比如批處理任務(wù)。當(dāng)然顽分,一切還是需要以實(shí)際測(cè)量結(jié)果為準(zhǔn)徐许。
啟用Parallel GC:
-XX:+UseParallelGC
命令行例子:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m
-XX:+UseParallelGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
另外,使用-XX:+UseParallelOldGC
命令行選項(xiàng)可以讓新生代和老年代同時(shí)使用多線程并行垃圾收集卒蘸。在老年代雌隅,ParallelOld是一種 標(biāo)記-整理 算法,會(huì)在標(biāo)記后將存活對(duì)象搬運(yùn)到一起來(lái)防止出現(xiàn)內(nèi)存碎片缸沃。用例如下:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m
-XX:+UseParallelOldGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
Concurrent Mark Sweep (CMS)
CMS 是一種希望最小化響應(yīng)時(shí)間的垃圾收集算法澄步,GC的某些步驟可以與應(yīng)用線程并行的進(jìn)行,它的收集周期為:
- 初始標(biāo)記(CMS-initial-mark) :標(biāo)記 Roots 能直接引用到的對(duì)象
- 并發(fā)標(biāo)記(CMS-concurrent-mark):進(jìn)行 GC Root Tracing
- 重新標(biāo)記(CMS-remark) :修正并發(fā)標(biāo)記期間由于用戶程序運(yùn)行而導(dǎo)致的變動(dòng)
- 并發(fā)清除(CMS-concurrent-sweep):進(jìn)行清除工作
初始標(biāo)記和重新標(biāo)記會(huì)導(dǎo)致 stop the world和泌。由于最耗時(shí)的并發(fā)標(biāo)記和并發(fā)清除都可以和用戶程序同時(shí)進(jìn)行村缸,所以其實(shí)可以認(rèn)為 GC 和用戶程序是同時(shí)進(jìn)行的。
CMS的一個(gè)劣勢(shì)是對(duì)CPU資源比較敏感武氓,不過(guò)現(xiàn)代的后端系統(tǒng)通常是重IO的梯皿,所以個(gè)人感覺(jué)影響并不會(huì)太大。另外一個(gè)問(wèn)題是县恕,由于 GC 和用戶程序同時(shí)進(jìn)行东羹,可能會(huì)有部分新產(chǎn)生的垃圾無(wú)法被直接回收,需要等到下一次 GC 時(shí)再回收忠烛。也是由于在垃圾收集階段用戶線程還需要運(yùn)行属提,那也就還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集美尸,需要預(yù)留一部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用冤议。使用-XX:CMSInitiatingOccupancyFraction
可以定義老年代使用了多少空間后就開(kāi)始進(jìn)行 GC,默認(rèn)為68%师坎。
CMS的在新生代與 Parallel GC 一樣使用并行復(fù)制收集器恕酸,而在老年代使用 標(biāo)記-清除 算法。在老年代胯陋,由于不會(huì)進(jìn)行整理操作蕊温,會(huì)導(dǎo)致空間碎片的出現(xiàn),如果空間碎片過(guò)多遏乔,則需要使用Serial Old進(jìn)行一次老年代垃圾回收义矛,為了防止該情況出現(xiàn),可以設(shè)置若干次CMS GC以后進(jìn)行一次內(nèi)存整理盟萨;同時(shí)凉翻,由于和應(yīng)用線程并行的進(jìn)行垃圾回收,所以需要在內(nèi)存耗盡前就開(kāi)始垃圾收集鸯旁,否則可能導(dǎo)致應(yīng)用無(wú)內(nèi)存可用噪矛。
相關(guān)參數(shù)介紹:
- 啟用CMS:-XX:+UseConcMarkSweepGC量蕊。
- CMS默認(rèn)回收線程數(shù)目是 (ParallelGCThreads + 3)/4) ,ParallelGCThreads是年輕代并行收集線程數(shù)目艇挨;如果你需要明確設(shè)定残炮,可以通過(guò)-XX:ParallelCMSThreads=2來(lái)設(shè)
- CMS階段整理內(nèi)存:-XX:+UseCMSCompactAtFullCollection;
- 設(shè)置每多少次CMS后進(jìn)行一次內(nèi)存整理:-XX:+CMSFullGCsBeforeCompaction=<次數(shù)>
- 老年代進(jìn)行CMS時(shí)的內(nèi)存消耗百分比缩滨,默認(rèn)為68:-XX:CMSInitiatingOccupancyFraction=75
G1 GC
使用 G1GC 時(shí)势就,Java 堆的內(nèi)存布局與其他的收集器有很大區(qū)別,它將整個(gè) Java 堆劃分成多個(gè)大小相等的獨(dú)立區(qū)域(Region)脉漏,雖然還是有年輕代和老年代的概念苞冯,但是新生代和老年代不是物理隔離的,它們都是一部分 Region的集合侧巨,并且這些Region無(wú)需在物理上連續(xù)舅锄。一般建議當(dāng)機(jī)器內(nèi)存較大(6G以上),且之前使用的GC不能滿足需求時(shí)才使用G1GC司忱。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型皇忿,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。 G1跟蹤各個(gè) Region 里面的垃圾堆積的價(jià)值大刑谷浴(回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值)鳍烁,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間繁扎,優(yōu)先回收價(jià)值最大的Region(這也就是Garbage-First名稱的來(lái)由)幔荒。 這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了 G1 收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率梳玫。
在G1收集器中爹梁,Region 之間的對(duì)象引用以及其他收集器中的新生代與老年代之間的對(duì)象引用,虛擬機(jī)都是使用 Remembered Set 來(lái)避免全堆掃描的汽纠。 G1 中每個(gè) Region 都有一個(gè)與之對(duì)應(yīng)的 Remembered Set卫键,虛擬機(jī)發(fā)現(xiàn)程序在對(duì) Reference 類型的數(shù)據(jù)進(jìn)行寫操作時(shí)傀履,會(huì)產(chǎn)生一個(gè) Write Barrier 暫時(shí)中斷寫操作虱朵,檢查Reference引用的對(duì)象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對(duì)象引用了新生代中的對(duì)象),如果是钓账,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中碴犬。 當(dāng)進(jìn)行內(nèi)存回收時(shí),在 GC 根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set 即可保證不對(duì)全堆掃描也不會(huì)有遺漏梆暮。
G1 垃圾回收的過(guò)程有下面幾步:
- 初始標(biāo)記(Initial Marking)
- 并發(fā)標(biāo)記(Concurrent Marking)
- 最終標(biāo)記(Final Marking)
- 篩選回收(Live Data Counting and Evacuation)
初始標(biāo)記階段僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象服协,這階段需要停頓線程,但耗時(shí)很短啦粹。 并發(fā)標(biāo)記階段是從GC Root開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析偿荷,找出存活的對(duì)象窘游,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行跳纳。 而最終標(biāo)記階段則是為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄忍饰,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程 Remembered Set Logs 里面,最終標(biāo)記階段需要把 Remembered Set Logs 的數(shù)據(jù)合并到 Remembered Set 中寺庄,這階段需要停頓線程艾蓝,但是可并行執(zhí)行。 最后在篩選回收階段首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序斗塘,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃赢织,這樣既能保證垃圾回收,又能保證停頓時(shí)間馍盟,而且也不會(huì)降低太多的吞吐量于置。
G1GC的相關(guān)參數(shù):
- -XX:+UseG1GC 使用G1GC
- -XX:MaxGCPauseMillis=n 最大暫停時(shí)間,jvm不保證做到 單位為毫秒
- -XX:InitiatingHeapOccupancyPercent=n 啟動(dòng)一個(gè)并發(fā)垃圾收集周期所需要達(dá)到的整堆占用比例贞岭。這個(gè)比例是指整個(gè)堆的占用比例而不是某一個(gè)代俱两,如果這個(gè)值是0則代表‘持續(xù)做GC’。默認(rèn)值是45
- -XX:MaxTenuringThreshold=n 經(jīng)過(guò)多少輪minor GC曹步,對(duì)象會(huì)進(jìn)入老年代
- -XX:ParallelGCThreads=n 并行階段使用線程數(shù)
- -XX:ConcGCThreads=n 并發(fā)垃圾收集的線程數(shù)目
- -XX:G1ReservePercent=n 防止晉升老年代失敗而預(yù)留的空閑區(qū)域的數(shù)目
- -XX:G1HeapRegionSize=n 空閑區(qū)域大小 最小1Mb 最大 32Mb.
3. 其他相關(guān)命令行選項(xiàng)
-Xms:初始堆大小宪彩,默認(rèn)為物理內(nèi)存的1/64(<1GB);默認(rèn)(MinHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存小于40%時(shí)讲婚,JVM就會(huì)增大堆直到-Xmx的最大限制
-Xmx:最大堆大小尿孔,默認(rèn)(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到 -Xms的最小限制
-Xmn:新生代的內(nèi)存空間大小筹麸,注意:此處的大小是(eden+ 2 survivor space)活合。與jmap -heap中顯示的New gen是不同的。在保證堆大小不變的情況下物赶,增大新生代后,將會(huì)減小老生代大小白指。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。
-XX:SurvivorRatio:新生代中Eden區(qū)域與Survivor區(qū)域的容量比值酵紫,默認(rèn)值為8告嘲。兩個(gè)Survivor區(qū)與一個(gè)Eden區(qū)的比值為1:1:8,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/10。
-Xss:每個(gè)線程的堆棧大小奖地。減小這個(gè)值可以增加最大線程數(shù)橄唬。一般小的應(yīng)用, 如果棧不是很深参歹, 應(yīng)該是128k夠用的仰楚,大的應(yīng)用建議使用256k。這個(gè)選項(xiàng)對(duì)性能影響比較大,一定要測(cè)試調(diào)優(yōu)僧界。
-XX:NewRatio:老年代與新生代的比例 比如3表明老年代大小為新生代3倍侨嘀。
-Xloggc:path 垃圾回收日志記錄位置
-XX:+PrintGCDateStamps 用真實(shí)日期打印垃圾回收時(shí)間
-XX:+PrintGCDetails 打印垃圾回收 詳情
-XX:+HeapDumpOnOutOfMemoryError OOM時(shí)候記錄heap dump
-XX:HeapDumpPath=C:\Users\millions\java_error_in_idea.hprof: heapdump的文件位置
4. 總結(jié)
jvm一直在發(fā)展,不同應(yīng)用也有各自的實(shí)際情況和應(yīng)用場(chǎng)景捂襟。調(diào)整應(yīng)用 GC 參數(shù)一定要從吞吐量和反應(yīng)時(shí)間兩方面考慮飒炎,以實(shí)際測(cè)試結(jié)果為準(zhǔn),而不能輕信任何人給的經(jīng)驗(yàn)結(jié)論笆豁。