要了解Java垃圾收集機(jī)制噪裕,先理解JVM內(nèi)存模式是非常重要的。今天我們將會(huì)了解JVM內(nèi)存的各個(gè)部分股毫、如何監(jiān)控以及垃圾收集調(diào)優(yōu)膳音。
Java(JVM)內(nèi)存模型
正如你從上面的圖片看到的,JVM內(nèi)存被分成多個(gè)獨(dú)立的部分铃诬。廣泛地說(shuō)祭陷,JVM堆內(nèi)存被分為兩部分——年輕代****(Young Generation)和老年代(Old Generation)****苍凛。
年輕代
年輕代是所有新對(duì)象產(chǎn)生的地方。當(dāng)年輕代內(nèi)存空間被用完時(shí)颗胡,就會(huì)觸發(fā)垃圾回收毫深。這個(gè)垃圾回收叫做Minor GC。年輕代被分為3個(gè)部分——Enden區(qū)和兩個(gè)Survivor區(qū)毒姨。
年輕代空間的要點(diǎn):
- 大多數(shù)新建的對(duì)象都位于Eden區(qū)哑蔫。
- 當(dāng)Eden區(qū)被對(duì)象填滿時(shí),就會(huì)執(zhí)行Minor GC弧呐。并把所有存活下來(lái)的對(duì)象轉(zhuǎn)移到其中一個(gè)survivor區(qū)闸迷。
- Minor GC同樣會(huì)檢查存活下來(lái)的對(duì)象,并把它們轉(zhuǎn)移到另一個(gè)survivor區(qū)俘枫。這樣在一段時(shí)間內(nèi)腥沽,總會(huì)有一個(gè)空的survivor區(qū)。
- 經(jīng)過(guò)多次GC周期后鸠蚪,仍然存活下來(lái)的對(duì)象會(huì)被轉(zhuǎn)移到年老代內(nèi)存空間今阳。通常這是在年輕代有資格提升到年老代前通過(guò)設(shè)定年齡閾值來(lái)完成的。
年老代
年老代內(nèi)存里包含了長(zhǎng)期存活的對(duì)象和經(jīng)過(guò)多次Minor GC后依然存活下來(lái)的對(duì)象茅信。通常會(huì)在老年代內(nèi)存被占滿時(shí)進(jìn)行垃圾回收盾舌。老年代的垃圾收集叫做Major GC。Major GC會(huì)花費(fèi)更多的時(shí)間蘸鲸。
Stop the World事件
所有的垃圾收集都是“Stop the World”事件妖谴,因?yàn)樗械膽?yīng)用線程都會(huì)停下來(lái)直到操作完成(所以叫“Stop the World”)。
因?yàn)槟贻p代里的對(duì)象都是一些臨時(shí)(short-lived )對(duì)象酌摇,執(zhí)行Minor GC非诚ゾ耍快,所以應(yīng)用不會(huì)受到(“Stop the World”)影響窑多。
由于Major GC會(huì)檢查所有存活的對(duì)象仍稀,因此會(huì)花費(fèi)更長(zhǎng)的時(shí)間。應(yīng)該盡量減少M(fèi)ajor GC埂息。因?yàn)镸ajor GC會(huì)在垃圾回收期間讓你的應(yīng)用反應(yīng)遲鈍琳轿,所以如果你有一個(gè)需要快速響應(yīng)的應(yīng)用發(fā)生多次Major GC,你會(huì)看到超時(shí)錯(cuò)誤耿芹。
垃圾回收時(shí)間取決于垃圾回收策略崭篡。這就是為什么有必要去監(jiān)控垃圾收集和對(duì)垃圾收集進(jìn)行調(diào)優(yōu)。從而避免要求快速響應(yīng)的應(yīng)用出現(xiàn)超時(shí)錯(cuò)誤吧秕。
永久代
永久代或者“Perm Gen”包含了JVM需要的應(yīng)用元數(shù)據(jù)琉闪,這些元數(shù)據(jù)描述了在應(yīng)用里使用的類(lèi)和方法。注意砸彬,永久代不是Java堆內(nèi)存的一部分颠毙。
永久代存放JVM運(yùn)行時(shí)使用的類(lèi)斯入。永久代同樣包含了Java SE庫(kù)的類(lèi)和方法。永久代的對(duì)象在full GC時(shí)進(jìn)行垃圾收集蛀蜜。
方法區(qū)
方法區(qū)是永久代空間的一部分刻两,并用來(lái)存儲(chǔ)類(lèi)型信息(運(yùn)行時(shí)常量和靜態(tài)變量)和方法代碼和構(gòu)造函數(shù)代碼。
內(nèi)存池
如果JVM實(shí)現(xiàn)支持滴某,JVM內(nèi)存管理會(huì)為創(chuàng)建內(nèi)存池磅摹,用來(lái)為不變對(duì)象創(chuàng)建對(duì)象池。字符串池就是內(nèi)存池類(lèi)型的一個(gè)很好的例子霎奢。內(nèi)存池可以屬于堆或者永久代户誓,這取決于JVM內(nèi)存管理的實(shí)現(xiàn)。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是每個(gè)類(lèi)常量池的運(yùn)行時(shí)代表幕侠。它包含了類(lèi)的運(yùn)行時(shí)常量和靜態(tài)方法帝美。運(yùn)行時(shí)常量池是方法區(qū)的一部分。
Java棧內(nèi)存
Java棧內(nèi)存用于運(yùn)行線程晤硕。它們包含了方法里的臨時(shí)數(shù)據(jù)悼潭、堆里其它對(duì)象引用的特定數(shù)據(jù)。你可以閱讀棧內(nèi)存和堆內(nèi)存的區(qū)別舞箍。
Java 堆內(nèi)存開(kāi)關(guān)
Java提供了大量的內(nèi)存開(kāi)關(guān)(參數(shù))舰褪,我們可以用它來(lái)設(shè)置內(nèi)存大小和它們的比例。下面是一些常用的開(kāi)關(guān):
| VM 開(kāi)關(guān) | VM 開(kāi)關(guān)描述 |
| -Xms | 設(shè)置JVM啟動(dòng)時(shí)堆的初始化大小创译。 |
| -Xmx | 設(shè)置堆最大值抵知。 |
| -Xmn | 設(shè)置年輕代的空間大小墙基,剩下的為老年代的空間大小软族。 |
| -XX:PermGen | 設(shè)置永久代內(nèi)存的初始化大小。 |
| -XX:MaxPermGen | 設(shè)置永久代的最大值残制。 |
| -XX:SurvivorRatio | 提供Eden區(qū)和survivor區(qū)的空間比例立砸。比如,如果年輕代的大小為10m并且VM開(kāi)關(guān)是-XX:SurvivorRatio=2初茶,那么將會(huì)保留5m內(nèi)存給Eden區(qū)和每個(gè)Survivor區(qū)分配2.5m內(nèi)存颗祝。默認(rèn)比例是8。 |
| -XX:NewRatio | 提供年老代和年輕代的比例大小恼布。默認(rèn)值是2螺戳。 |
大多數(shù)時(shí)候,上面的選項(xiàng)已經(jīng)足夠使用了折汞。但是如果你還想了解其他的選項(xiàng)倔幼,那么請(qǐng)查看JVM選項(xiàng)官方網(wǎng)頁(yè)。
Java垃圾回收
Java垃圾回收會(huì)找出沒(méi)用的對(duì)象爽待,把它從內(nèi)存中移除并釋放出內(nèi)存給以后創(chuàng)建的對(duì)象使用损同。Java程序語(yǔ)言中的一個(gè)最大優(yōu)點(diǎn)是自動(dòng)垃圾回收翩腐,不像其他的程序語(yǔ)言那樣需要手動(dòng)分配和釋放內(nèi)存,比如C語(yǔ)言膏燃。
垃圾收集器是一個(gè)后臺(tái)運(yùn)行程序茂卦。它管理著內(nèi)存中的所有對(duì)象并找出沒(méi)被引用的對(duì)象。所有的這些未引用的對(duì)象都會(huì)被刪除组哩,回收它們的空間并分配給其他對(duì)象等龙。
一個(gè)基本的垃圾回收過(guò)程涉及三個(gè)步驟:
- 標(biāo)記:這是第一步。在這一步禁炒,垃圾收集器會(huì)找出哪些對(duì)象正在使用和哪些對(duì)象不在使用而咆。
- 正常清除:垃圾收集器清會(huì)除不在使用的對(duì)象,回收它們的空間分配給其他對(duì)象幕袱。
- 壓縮清除:為了提升性能暴备,壓縮清除會(huì)在刪除沒(méi)用的對(duì)象后,把所有存活的對(duì)象移到一起们豌。這樣可以提高分配新對(duì)象的效率涯捻。
簡(jiǎn)單標(biāo)記和清除方法存在兩個(gè)問(wèn)題:
- 效率很低。因?yàn)榇蠖鄶?shù)新建對(duì)象都會(huì)成為“沒(méi)用對(duì)象”望迎。
- 經(jīng)過(guò)多次垃圾回收周期的對(duì)象很有可能在以后的周期也會(huì)存活下來(lái)障癌。
上面簡(jiǎn)單清除方法的問(wèn)題在于Java垃圾收集的分代回收的,而且在堆內(nèi)存里有年輕代和年老代兩個(gè)區(qū)域辩尊。我已經(jīng)在上面解釋了Minor GC和Major GC是怎樣掃描對(duì)象涛浙,以及如何把對(duì)象從一個(gè)分代空間移到另外一個(gè)分代空間。
Java垃圾回收類(lèi)型
這里有五種可以在應(yīng)用里使用的垃圾回收類(lèi)型摄欲。僅需要使用JVM開(kāi)關(guān)就可以在我們的應(yīng)用里啟用垃圾回收策略轿亮。讓我們一起來(lái)逐一了解:
- Serial GC(-XX:+UseSerialGC):Serial GC使用簡(jiǎn)單的標(biāo)記、清除胸墙、壓縮方法對(duì)年輕代和年老代進(jìn)行垃圾回收我注,即Minor GC和Major GC。Serial GC在client模式(客戶(hù)端模式)很有用迟隅,比如在簡(jiǎn)單的獨(dú)立應(yīng)用和CPU配置較低的機(jī)器但骨。這個(gè)模式對(duì)占有內(nèi)存較少的應(yīng)用很管用。
- Parallel GC(-XX:+UseParallelGC):除了會(huì)產(chǎn)生N個(gè)線程來(lái)進(jìn)行年輕代的垃圾收集外智袭,Parallel GC和Serial GC幾乎一樣奔缠。這里的N是系統(tǒng)CPU的核數(shù)。我們可以使用 -XX:ParallelGCThreads=n 這個(gè)JVM選項(xiàng)來(lái)控制線程數(shù)量吼野。并行垃圾收集器也叫throughput收集器校哎。因?yàn)樗褂昧硕郈PU加快垃圾回收性能。Parallel GC在進(jìn)行年老代垃圾收集時(shí)使用單線程箫锤。
- Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一樣贬蛙。不同之處雨女,Parallel Old GC在年輕代垃圾收集和年老代垃圾回收時(shí)都使用多線程收集。
- 并發(fā)標(biāo)記清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被稱(chēng)為短暫停頓并發(fā)收集器阳准。它是對(duì)年老代進(jìn)行垃圾收集的氛堕。CMS收集器通過(guò)多線程并發(fā)進(jìn)行垃圾回收,盡量減少垃圾收集造成的停頓野蝇。CMS收集器對(duì)年輕代進(jìn)行垃圾回收使用的算法和Parallel收集器一樣讼稚。這個(gè)垃圾收集器適用于不能忍受長(zhǎng)時(shí)間停頓要求快速響應(yīng)的應(yīng)用∪粕颍可使用 -XX:ParallelCMSThreads=n JVM選項(xiàng)來(lái)限制CMS收集器的線程數(shù)量锐想。
- G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才可以使用的特性,它的長(zhǎng)遠(yuǎn)目標(biāo)時(shí)代替CMS收集器乍狐。G1收集器是一個(gè)并行的赠摇、并發(fā)的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器運(yùn)行方式不一樣浅蚪,不區(qū)分年輕代和年老代空間藕帜。它把堆空間劃分為多個(gè)大小相等的區(qū)域。當(dāng)進(jìn)行垃圾收集時(shí)惜傲,它會(huì)優(yōu)先收集存活對(duì)象較少的區(qū)域洽故,因此叫“Garbage First”。你可以在Oracle Garbage-FIrst收集器文檔找到更多詳細(xì)信息盗誊。
Java垃圾收集監(jiān)控
我們可以使用命令行和圖形工具來(lái)監(jiān)控監(jiān)控應(yīng)用垃圾回收时甚。例如,我使用Java SE下載頁(yè)中的一個(gè)demo來(lái)實(shí)驗(yàn)哈踱。
如果你想使用同樣的應(yīng)用荒适,可以到Java SE下載頁(yè)面下載JDK 7和JavaFX演示和示例。我使用的示例應(yīng)用是Java2Demo.jar嚣鄙,它位于 jdk1.7.0_55/demo/jfc/Java2D 目錄下吻贿。這只是一個(gè)可選步驟串结,你可以運(yùn)行GC監(jiān)控命令監(jiān)控任何Java應(yīng)用哑子。
我打開(kāi)演示應(yīng)用使用的命令是:
pankaj@Pankaj:~``/Downloads/jdk1``.7.0_55``/demo/jfc/Java2D``$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jsat
可以使用jstat命令行工具監(jiān)控JVM內(nèi)存和垃圾回收。標(biāo)準(zhǔn)的JDK已經(jīng)附帶了jstat肌割,所以不需要做任何額外的事情就可以得到它卧蜓。
要運(yùn)行jstat你需要知道應(yīng)用的進(jìn)程id,你可以使用 ps -eaf | grep java 命令獲取進(jìn)程id把敞。
pankaj@Pankaj:~$
ps
-eaf |
grep
Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66
/usr/bin/java
-Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00
grep
Java2Demo.jar
從上面知道弥奸,我的Java應(yīng)用進(jìn)程id是9582。現(xiàn)在可以運(yùn)行jstat命令了奋早,就像下面展示的一樣:
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
jstat命令的最后一個(gè)參數(shù)是每個(gè)輸出的時(shí)間間隔盛霎。每隔一秒就會(huì)打印出內(nèi)存和垃圾收集數(shù)據(jù)赠橙。
讓我們一起來(lái)對(duì)每一列的意義進(jìn)行逐一了解:
- S0C和S1C:這一列展示了Survivor0和Survivor1區(qū)的當(dāng)前大小(單位KB)愤炸。
- S0U和S1U:這一列展示了當(dāng)前Survivor0和Survivor1區(qū)的使用情況(單位KB)期揪。注意:無(wú)論任何時(shí)候,總會(huì)有一個(gè)Survivor區(qū)是空著的规个。
- EC和EU:這些列展示了Eden區(qū)當(dāng)前空間大小和使用情況(單位KB)凤薛。注意:EU的大小一直在增大浊猾。而且只要大小接近EC時(shí)羹呵,就會(huì)觸發(fā)Minor GC并且EU將會(huì)減小。
- OC和OU:這些列展示了年老代當(dāng)前空間大小和當(dāng)前使用情況(單位KB)坛怪。
- PC和PU:這些列展示了Perm Gen(永久代)當(dāng)前空間大小和當(dāng)前使用情況(單位KB)墅拭。
- YGC和YGCT:YGC這列顯示了發(fā)生在年輕代的GC事件的數(shù)量活玲。YGCT這列顯示了在年輕代進(jìn)行GC操作的累計(jì)時(shí)間。注意:在EU的值由于minor GC導(dǎo)致下降時(shí)谍婉,同一行的YGC和YGCT都會(huì)增加翼虫。
- FGC和FGCT:FGC列顯示了發(fā)生Full GC事件的次數(shù)。FGCT顯示了進(jìn)行Full GC操作的累計(jì)時(shí)間屡萤。注意:相對(duì)于年輕代的GC使用時(shí)間珍剑,F(xiàn)ull GC所用的時(shí)間長(zhǎng)很多。
- GCT:這一列顯示了GC操作的總累計(jì)時(shí)間死陆。注意:總累計(jì)時(shí)間是YGCT和FGCT兩列所用時(shí)間的總和(GCT=YGCT+FGCT)招拙。
jstat的優(yōu)點(diǎn),我們同樣可以在沒(méi)有GUI的遠(yuǎn)程服務(wù)器上運(yùn)行jstat措译。注意:我們是通過(guò) -Xmn10m 選項(xiàng)來(lái)指定S0C别凤、S1C和EC的總和為10m的。
Java VisualVM及Visual GC插件
如果你想在GUI里查看內(nèi)存和GC领虹,那么可以使用jvisualvm工具规哪。Java VisualVM同樣是JDK的一部分,所以你不需要單獨(dú)去下載塌衰。
在終端運(yùn)行jvisualvm命令啟動(dòng)Java VisualVM程序诉稍。一旦啟動(dòng)程序,你需要從Tools->Plugins選項(xiàng)安裝Visual GC插件最疆,就像下面圖片展示的杯巨。
安裝完Visual GC插件后,從左邊欄打開(kāi)應(yīng)用并把視角轉(zhuǎn)到Visual GC部分努酸。你將會(huì)得到關(guān)于JVM內(nèi)存和垃圾收集詳情服爷,如下圖所示。
Java垃圾回收調(diào)優(yōu)
Java垃圾回收調(diào)優(yōu)應(yīng)該是提升應(yīng)用吞吐量的最后一個(gè)選擇。在你發(fā)現(xiàn)應(yīng)用由于長(zhǎng)時(shí)間垃圾回收導(dǎo)致了應(yīng)用性能下降仍源、出現(xiàn)超時(shí)的時(shí)候心褐,應(yīng)該考慮Java垃圾收集調(diào)優(yōu)。
如果你在日志里看到 java.lang.OutOfMemoryError: PermGen space錯(cuò)誤笼踩,那么可以嘗試使用 -XX:PermGen 和 -XX:MaxPermGen JVM選項(xiàng)去監(jiān)控并增加Perm Gen內(nèi)存空間檬寂。你也可以嘗試使用-XX:+CMSClassUnloadingEnabled并查看使用CMS垃圾收集器的執(zhí)行性能。
如果你看到了大量的Full GC操作戳表,那么你應(yīng)該嘗試增大老年代的內(nèi)存空間桶至。
全面垃圾收集調(diào)優(yōu)要花費(fèi)大量的努力和時(shí)間,這里沒(méi)有一塵不變的硬性調(diào)優(yōu)規(guī)則匾旭。你需要去嘗試不同的選項(xiàng)并且對(duì)這些選項(xiàng)進(jìn)行對(duì)比镣屹,從而找出最適合自己應(yīng)用的方案。
這就是所有的Java內(nèi)存模型和垃圾回收內(nèi)容价涝。希望對(duì)你理解JVM內(nèi)存和垃圾收集過(guò)程有所幫助女蜈。
原文鏈接: journaldev 翻譯: ImportNew.com - 進(jìn)林
譯文鏈接: http://www.importnew.com/14086.html