垃圾收集基礎(chǔ)
Java 語(yǔ)言的一大特點(diǎn)就是可以進(jìn)行自動(dòng)垃圾回收處理尔觉,而無(wú)需開發(fā)人員過(guò)于關(guān)注系統(tǒng)資源,例如內(nèi)存資源的釋放情況芥吟。自動(dòng)垃圾收集雖然大大減輕了開發(fā)人員的工作量,但是也增加了軟件系統(tǒng)的負(fù)擔(dān)钉稍。
擁有垃圾收集器可以說(shuō)是 Java 語(yǔ)言與 C++語(yǔ)言的一項(xiàng)顯著區(qū)別棺耍。在 C++語(yǔ)言中,程序員必須小心謹(jǐn)慎地處理每一項(xiàng)內(nèi)存分配俊卤,且內(nèi)存使用完后必須手工釋放曾經(jīng)占用的內(nèi)存空間。當(dāng)內(nèi)存釋放不夠完全時(shí)岂昭,即存在分配但永不釋放的內(nèi)存塊,就會(huì)引起內(nèi)存泄漏约啊,嚴(yán)重時(shí)甚至導(dǎo)致程序癱瘓取董。
以下列舉了垃圾回收器常用的算法及實(shí)驗(yàn)原理:
引用計(jì)數(shù)法 (Reference Counting)
引用計(jì)數(shù)器在微軟的 COM 組件技術(shù)中、Adobe 的 ActionScript3 種都有使用枢里。
引用計(jì)數(shù)器的實(shí)現(xiàn)很簡(jiǎn)單蹂午,對(duì)于一個(gè)對(duì)象 A,只要有任何一個(gè)對(duì)象引用了 A奥洼,則 A 的引用計(jì)數(shù)器就加 1晚胡,當(dāng)引用失效時(shí),引用計(jì)數(shù)器就減 1估盘。只要對(duì)象 A 的引用計(jì)數(shù)器的值為 0,則對(duì)象 A 就不可能再被使用擅编。
引用計(jì)數(shù)器的實(shí)現(xiàn)也非常簡(jiǎn)單箫踩,只需要為每個(gè)對(duì)象配置一個(gè)整形的計(jì)數(shù)器即可。但是引用計(jì)數(shù)器有一個(gè)嚴(yán)重的問(wèn)題锦担,即無(wú)法處理循環(huán)引用的情況慨削。因此鱼的,在 Java 的垃圾回收器中沒(méi)有使用這種算法凑阶。
一個(gè)簡(jiǎn)單的循環(huán)引用問(wèn)題描述如下:有對(duì)象 A 和對(duì)象 B衷快,對(duì)象 A 中含有對(duì)象 B 的引用,對(duì)象 B 中含有對(duì)象 A 的引用蘸拔。此時(shí),對(duì)象 A 和對(duì)象 B 的引用計(jì)數(shù)器都不為 0宝冕。但是在系統(tǒng)中卻不存在任何第 3 個(gè)對(duì)象引用了 A 或 B邓萨。也就是說(shuō),A 和 B 是應(yīng)該被回收的垃圾對(duì)象宝剖,但由于垃圾對(duì)象間相互引用歉甚,從而使垃圾回收器無(wú)法識(shí)別,引起內(nèi)存泄漏赖钞。
標(biāo)記-清除算法 (Mark-Sweep)
標(biāo)記-清除算法將垃圾回收分為兩個(gè)階段:標(biāo)記階段和清除階段聘裁。一種可行的實(shí)現(xiàn)是,在標(biāo)記階段首先通過(guò)根節(jié)點(diǎn)咧虎,標(biāo)記所有從根節(jié)點(diǎn)開始的較大對(duì)象计呈。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象茁彭。然后扶歪,在清除階段摄闸,清除所有未被標(biāo)記的對(duì)象年枕。該算法最大的問(wèn)題是存在大量的空間碎片乎完,因?yàn)榛厥蘸蟮目臻g是不連續(xù)的。在對(duì)象的堆空間分配過(guò)程中树姨,尤其是大對(duì)象的內(nèi)存分配,不連續(xù)的內(nèi)存空間的工作效率要低于連續(xù)的空間硝清。
復(fù)制算法 (Copying)
將現(xiàn)有的內(nèi)存空間分為兩快转晰,每次只使用其中一塊,在垃圾回收時(shí)將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未被使用的內(nèi)存塊中防嗡,之后侠坎,清除正在使用的內(nèi)存塊中的所有對(duì)象,交換兩個(gè)內(nèi)存的角色他嫡,完成垃圾回收庐完。
如果系統(tǒng)中的垃圾對(duì)象很多,復(fù)制算法需要復(fù)制的存活對(duì)象數(shù)量并不會(huì)太大门躯。因此在真正需要垃圾回收的時(shí)刻,復(fù)制算法的效率是很高的染乌。又由于對(duì)象在垃圾回收過(guò)程中統(tǒng)一被復(fù)制到新的內(nèi)存空間中懂讯,因此,可確崩兆回收后的內(nèi)存空間是沒(méi)有碎片的。該算法的缺點(diǎn)是將系統(tǒng)內(nèi)存折半实蔽。
Java 的新生代串行垃圾回收器中使用了復(fù)制算法的思想。新生代分為 eden 空間玩荠、from 空間贼邓、to 空間 3 個(gè)部分。其中 from 空間和 to 空間可以視為用于復(fù)制的兩塊大小相同塑径、地位相等,且可進(jìn)行角色互換的空間塊匆骗。from 和 to 空間也稱為 survivor 空間誉简,即幸存者空間,用于存放未被回收的對(duì)象瓮钥。
在垃圾回收時(shí)烹吵,eden 空間中的存活對(duì)象會(huì)被復(fù)制到未使用的 survivor 空間中 (假設(shè)是 to),正在使用的 survivor 空間 (假設(shè)是 from) 中的年輕對(duì)象也會(huì)被復(fù)制到 to 空間中 (大對(duì)象锈津,或者老年對(duì)象會(huì)直接進(jìn)入老年帶凉蜂,如果 to 空間已滿,則對(duì)象也會(huì)直接進(jìn)入老年代)窿吩。此時(shí),eden 空間和 from 空間中的剩余對(duì)象就是垃圾對(duì)象蛉顽,可以直接清空先较,to 空間則存放此次回收后的存活對(duì)象。這種改進(jìn)的復(fù)制算法既保證了空間的連續(xù)性闲勺,又避免了大量的內(nèi)存空間浪費(fèi)。
標(biāo)記-壓縮算法 (Mark-Compact)
復(fù)制算法的高效性是建立在存活對(duì)象少翘地、垃圾對(duì)象多的前提下的癌幕。這種情況在年輕代經(jīng)常發(fā)生,但是在老年代更常見(jiàn)的情況是大部分對(duì)象都是存活對(duì)象橙喘。如果依然使用復(fù)制算法,由于存活的對(duì)象較多厅瞎,復(fù)制的成本也將很高初坠。
標(biāo)記-壓縮算法是一種老年代的回收算法,它在標(biāo)記-清除算法的基礎(chǔ)上做了一些優(yōu)化锁保。也首先需要從根節(jié)點(diǎn)開始對(duì)所有可達(dá)對(duì)象做一次標(biāo)記半沽,但之后,它并不簡(jiǎn)單地清理未標(biāo)記的對(duì)象霉赡,而是將所有的存活對(duì)象壓縮到內(nèi)存的一端幔托。之后,清理邊界外所有的空間嗓化。這種方法既避免了碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間刺覆,因此史煎,其性價(jià)比比較高驳糯。
增量算法 (Incremental Collecting)
在垃圾回收過(guò)程中酝枢,應(yīng)用軟件將處于一種 CPU 消耗很高的狀態(tài)悍手。在這種 CPU 消耗很高的狀態(tài)下,應(yīng)用程序所有的線程都會(huì)掛起竣付,暫停一切正常的工作滞欠,等待垃圾回收的完成。如果垃圾回收時(shí)間過(guò)長(zhǎng)仑撞,應(yīng)用程序會(huì)被掛起很久,將嚴(yán)重影響用戶體驗(yàn)或者系統(tǒng)的穩(wěn)定性桶良。
增量算法的基本思想是沮翔,如果一次性將所有的垃圾進(jìn)行處理,需要造成系統(tǒng)長(zhǎng)時(shí)間的停頓疲牵,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替執(zhí)行榆鼠。每次,垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間识啦,接著切換到應(yīng)用程序線程。依次反復(fù)颓哮,直到垃圾收集完成鸵荠。使用這種方式,由于在垃圾回收過(guò)程中姨伤,間斷性地還執(zhí)行了應(yīng)用程序代碼,所以能減少系統(tǒng)的停頓時(shí)間齿税。但是炊豪,因?yàn)榫€程切換和上下文轉(zhuǎn)換的消耗拧篮,會(huì)使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降串绩。
分代 (Generational Collecting)
根據(jù)垃圾回收對(duì)象的特性,不同階段最優(yōu)的方式是使用合適的算法用于本階段的垃圾回收高氮,分代算法即是基于這種思想顷牌,它將內(nèi)存區(qū)間根據(jù)對(duì)象的特點(diǎn)分成幾塊,根據(jù)每塊內(nèi)存區(qū)間的特點(diǎn)罪裹,使用不同的回收算法运挫,以提高垃圾回收的效率。以 Hot Spot 虛擬機(jī)為例峡继,它將所有的新建對(duì)象都放入稱為年輕代的內(nèi)存區(qū)域匈挖,年輕代的特點(diǎn)是對(duì)象會(huì)很快回收,因此关划,在年輕代就選擇效率較高的復(fù)制算法。當(dāng)一個(gè)對(duì)象經(jīng)過(guò)幾次回收后依然存活裤翩,對(duì)象就會(huì)被放入稱為老生代的內(nèi)存空間。在老生代中呵扛,幾乎所有的對(duì)象都是經(jīng)過(guò)幾次垃圾回收后依然得以幸存的筐带。因此,可以認(rèn)為這些對(duì)象在一段時(shí)期內(nèi)蓝晒,甚至在應(yīng)用程序的整個(gè)生命周期中帖鸦,將是常駐內(nèi)存的。如果依然使用復(fù)制算法回收老生代洛二,將需要復(fù)制大量對(duì)象攻锰。再加上老生代的回收性價(jià)比也要低于新生代,因此這種做法也是不可取的娶吞。根據(jù)分代的思想,可以對(duì)老年代的回收使用與新生代不同的標(biāo)記-壓縮算法娇斑,以提高垃圾回收效率材部。
從不同角度分析垃圾收集器,可以將其分為不同的類型苦丁。
1. 按線程數(shù)分物臂,可以分為串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一個(gè)線程進(jìn)行垃圾回收蛾狗;并行垃圾回收器一次將開啟多個(gè)線程同時(shí)進(jìn)行垃圾回收仪媒。在并行能力較強(qiáng)的 CPU 上,使用并行垃圾回收器可以縮短 GC 的停頓時(shí)間。
2. 按照工作模式分佃扼,可以分為并發(fā)式垃圾回收器和獨(dú)占式垃圾回收器蔼夜。并發(fā)式垃圾回收器與應(yīng)用程序線程交替工作,以盡可能減少應(yīng)用程序的停頓時(shí)間瘤运;獨(dú)占式垃圾回收器 (Stop the world) 一旦運(yùn)行遵倦,就停止應(yīng)用程序中的其他所有線程官撼,直到垃圾回收過(guò)程完全結(jié)束。
3. 按碎片處理方式可分為壓縮式垃圾回收器和非壓縮式垃圾回收器掠哥。壓縮式垃圾回收器會(huì)在回收完成后秃诵,對(duì)存活對(duì)象進(jìn)行壓縮整理,消除回收后的碎片菠净;非壓縮式的垃圾回收器不進(jìn)行這步操作。
4. 按工作的內(nèi)存區(qū)間牵咙,又可分為新生代垃圾回收器和老年代垃圾回收器攀唯。
可以用以下指標(biāo)評(píng)價(jià)一個(gè)垃圾處理器的好壞。
吞吐量:指在應(yīng)用程序的生命周期內(nèi)另凌,應(yīng)用程序所花費(fèi)的時(shí)間和系統(tǒng)總運(yùn)行時(shí)間的比值戒幔。系統(tǒng)總運(yùn)行時(shí)間=應(yīng)用程序耗時(shí)+GC 耗時(shí)。如果系統(tǒng)運(yùn)行了 100min工坊,GC 耗時(shí) 1min,那么系統(tǒng)的吞吐量就是 (100-1)/100=99%雀瓢。
垃圾回收器負(fù)載:和吞吐量相反,垃圾回收器負(fù)載指來(lái)記回收器耗時(shí)與系統(tǒng)運(yùn)行總時(shí)間的比值刃麸。
停頓時(shí)間:指垃圾回收器正在運(yùn)行時(shí)司浪,應(yīng)用程序的暫停時(shí)間。對(duì)于獨(dú)占回收器而言吁伺,停頓時(shí)間可能會(huì)比較長(zhǎng)租谈。使用并發(fā)的回收器時(shí),由于垃圾回收器和應(yīng)用程序交替運(yùn)行窟却,程序的停頓時(shí)間會(huì)變短呻逆,但是,由于其效率很可能不如獨(dú)占垃圾回收器咖城,故系統(tǒng)的吞吐量可能會(huì)較低。
垃圾回收頻率:指垃圾回收器多長(zhǎng)時(shí)間會(huì)運(yùn)行一次切平。一般來(lái)說(shuō)州袒,對(duì)于固定的應(yīng)用而言,垃圾回收器的頻率應(yīng)該是越低越好他匪。通常增大堆空間可以有效降低垃圾回收發(fā)生的頻率夸研,但是可能會(huì)增加回收產(chǎn)生的停頓時(shí)間。
反應(yīng)時(shí)間:指當(dāng)一個(gè)對(duì)象被稱為垃圾后多長(zhǎng)時(shí)間內(nèi)悼沈,它所占據(jù)的內(nèi)存空間會(huì)被釋放贱迟。
堆分配:不同的垃圾回收器對(duì)堆內(nèi)存的分配方式可能是不同的衣吠。一個(gè)良好的垃圾收集器應(yīng)該有一個(gè)合理的堆內(nèi)存區(qū)間劃分壤靶。
JVM 垃圾回收器分類
新生代串行收集器
串行收集器主要有兩個(gè)特點(diǎn):第一,它僅僅使用單線程進(jìn)行垃圾回收忧换;第二向拆,它獨(dú)占式的垃圾回收。
在串行收集器進(jìn)行垃圾回收時(shí)刹缝,Java 應(yīng)用程序中的線程都需要暫停奖蔓,等待垃圾回收的完成讹堤,這樣給用戶體驗(yàn)造成較差效果。雖然如此洲守,串行收集器卻是一個(gè)成熟、經(jīng)過(guò)長(zhǎng)時(shí)間生產(chǎn)環(huán)境考驗(yàn)的極為高效的收集器知允。新生代串行處理器使用復(fù)制算法叙谨,實(shí)現(xiàn)相對(duì)簡(jiǎn)單,邏輯處理特別高效涤垫,且沒(méi)有線程切換的開銷竟终。在諸如單 CPU 處理器或者較小的應(yīng)用內(nèi)存等硬件平臺(tái)不是特別優(yōu)越的場(chǎng)合,它的性能表現(xiàn)可以超過(guò)并行回收器和并發(fā)回收器统捶。在 HotSpot 虛擬機(jī)中榆芦,使用-XX:+UseSerialGC 參數(shù)可以指定使用新生代串行收集器和老年代串行收集器。當(dāng) JVM 在 Client 模式下運(yùn)行時(shí)驻右,它是默認(rèn)的垃圾收集器崎淳。一次新生代串行收集器的工作輸出日志類似如清單 1 信息 (使用-XX:+PrintGCDetails 開關(guān)) 所示。
清單 1. 一次新生代串行收集器的工作輸出日志
[GC [DefNew: 3468K->150K(9216K), 0.0028638 secs][Tenured:
1562K->1712K(10240K), 0.0084220 secs] 3468K->1712K(19456K),
[Perm : 377K->377K(12288K)],
0.0113816 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
它顯示了一次垃圾回收前的新生代的內(nèi)存占用量和垃圾回收后的新生代內(nèi)存占用量茵瘾,以及垃圾回收所消耗的時(shí)間咐鹤。
老年代串行收集器
老年代串行收集器使用的是標(biāo)記-壓縮算法。和新生代串行收集器一樣雕旨,它也是一個(gè)串行的捧请、獨(dú)占式的垃圾回收器。由于老年代垃圾回收通常會(huì)使用比新生代垃圾回收更長(zhǎng)的時(shí)間疹蛉,因此,在堆空間較大的應(yīng)用程序中育韩,一旦老年代串行收集器啟動(dòng)闺鲸,應(yīng)用程序很可能會(huì)因此停頓幾秒甚至更長(zhǎng)時(shí)間。雖然如此悉罕,老年代串行回收器可以和多種新生代回收器配合使用立镶,同時(shí)它也可以作為 CMS 回收器的備用回收器。若要啟用老年代串行回收器谜慌,可以嘗試使用以下參數(shù):-XX:+UseSerialGC: 新生代、老年代都使用串行回收器变泄。
清單 2. 一次老年代串行收集器的工作輸出日志
Heap
def new generation total 4928K, used 1373K [0x27010000, 0x27560000, 0x2c560000)
eden space 4416K, 31% used [0x27010000, 0x27167628, 0x27460000)
from space 512K, 0% used [0x27460000, 0x27460000, 0x274e0000)
to space 512K, 0% used [0x274e0000, 0x274e0000, 0x27560000)
tenured generation total 10944K, used 0K [0x2c560000, 0x2d010000, 0x37010000)
the space 10944K, 0% used [0x2c560000, 0x2c560000, 0x2c560200, 0x2d010000)
compacting perm gen total 12288K, used 376K [0x37010000, 0x37c10000, 0x3b010000)
the space 12288K, 3% used [0x37010000, 0x3706e0b8, 0x3706e200, 0x37c10000)
ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
如果使用-XX:+UseParNewGC 參數(shù)設(shè)置,表示新生代使用并行收集器妨蛹,老年代使用串行收集器,如清單 3 所示狠半。
清單 3. 一次串并行收集器混合使用的工作輸出日志
Heap
par new generation total 4928K, used 1373K [0x0f010000, 0x0f560000, 0x14560000)
eden space 4416K, 31% used [0x0f010000, 0x0f167620, 0x0f460000)
from space 512K, 0% used [0x0f460000, 0x0f460000, 0x0f4e0000)
to space 512K, 0% used [0x0f4e0000, 0x0f4e0000, 0x0f560000)
tenured generation total 10944K, used 0K [0x14560000, 0x15010000, 0x1f010000)
the space 10944K, 0% used [0x14560000, 0x14560000, 0x14560200, 0x15010000)
compacting perm gen total 12288K, used 2056K [0x1f010000, 0x1fc10000, 0x23010000)
the space 12288K, 16% used [0x1f010000, 0x1f2121d0, 0x1f212200, 0x1fc10000)
No shared spaces configured.
如果使用-XX:+UseParallelGC 參數(shù)設(shè)置神年,表示新生代和老年代均使用并行回收收集器。如清單 4 所示已日。
清單 4. 一次老年代并行回收器的工作輸出日志
[Full GC [Tenured: 1712K->1699K(10240K), 0.0071963 secs] 1712K->1699K(19456K),
[Perm : 377K->372K(12288K)], 0.0072393 secs] [Times: user=0.00 sys=0.00,
real=0.01 secs]
它顯示了垃圾回收前老年代和永久區(qū)的內(nèi)存占用量栅屏,以及垃圾回收后老年代和永久區(qū)的內(nèi)存使用量。
并行收集器
并行收集器是工作在新生代的垃圾收集器护奈,它只簡(jiǎn)單地將串行回收器多線程化哥纫。它的回收策略、算法以及參數(shù)和串行回收器一樣奖慌。
并行回收器也是獨(dú)占式的回收器,在收集過(guò)程中,應(yīng)用程序會(huì)全部暫停建椰。但由于并行回收器使用多線程進(jìn)行垃圾回收,因此屠列,在并發(fā)能力比較強(qiáng)的 CPU 上伞矩,它產(chǎn)生的停頓時(shí)間要短于串行回收器,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中苛让,并行回收器的效果不會(huì)比串行回收器好沟蔑,由于多線程的壓力瘦材,它的實(shí)際表現(xiàn)很可能比串行回收器差仿畸。
開啟并行回收器可以使用參數(shù)-XX:+UseParNewGC,該參數(shù)設(shè)置新生代使用并行收集器簿晓,老年代使用串行收集器千埃。
清單 5. 設(shè)置參數(shù)-XX:+UseParNewGC 的輸出日志
[GC [ParNew: 825K->161K(4928K), 0.0155258 secs][Tenured: 8704K->661K(10944K),
0.0071964 secs] 9017K->661K(15872K),
[Perm : 2049K->2049K(12288K)], 0.0228090 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
par new generation total 4992K, used 179K [0x0f010000, 0x0f570000, 0x14560000)
eden space 4480K, 4% used [0x0f010000, 0x0f03cda8, 0x0f470000)
from space 512K, 0% used [0x0f470000, 0x0f470000, 0x0f4f0000)
to space 512K, 0% used [0x0f4f0000, 0x0f4f0000, 0x0f570000)
tenured generation total 10944K, used 8853K [0x14560000, 0x15010000, 0x1f010000)
the space 10944K, 80% used [0x14560000, 0x14e057c0, 0x14e05800, 0x15010000)
compacting perm gen total 12288K, used 2060K [0x1f010000, 0x1fc10000, 0x23010000)
the space 12288K, 16% used [0x1f010000, 0x1f213228, 0x1f213400, 0x1fc10000)
No shared spaces configured.
設(shè)置參數(shù)-XX:+UseConcMarkSweepGC 可以要求新生代使用并行收集器,老年代使用 CMS皿曲。
清單 6. 設(shè)置參數(shù)-XX:+UseConcMarkSweepGC 的輸出日志
[GC [ParNew: 8967K->669K(14784K), 0.0040895 secs] 8967K->669K(63936K),
0.0043255 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 14784K, used 9389K [0x03f50000, 0x04f50000, 0x04f50000)
eden space 13184K, 66% used [0x03f50000, 0x047d3e58, 0x04c30000)
from space 1600K, 41% used [0x04dc0000, 0x04e67738, 0x04f50000)
to space 1600K, 0% used [0x04c30000, 0x04c30000, 0x04dc0000)
concurrent mark-sweep generation total 49152K, used 0K [0x04f50000, 0x07f50000, 0x09f50000)
concurrent-mark-sweep perm gen total 12288K, used 2060K [0x09f50000, 0x0ab50000, 0x0df50000)
并行收集器工作時(shí)的線程數(shù)量可以使用-XX:ParallelGCThreads 參數(shù)指定吴侦。一般,最好與 CPU 數(shù)量相當(dāng)劫樟,避免過(guò)多的線程數(shù)影響垃圾收集性能织堂。在默認(rèn)情況下,當(dāng) CPU 數(shù)量小于 8 個(gè)易阳,ParallelGCThreads 的值等于 CPU 數(shù)量,大于 8 個(gè)拒课,ParallelGCThreads 的值等于 3+[5*CPU_Count]/8]事示。以下測(cè)試顯示了筆者筆記本上運(yùn)行 8 個(gè)線程時(shí)耗時(shí)最短,本人筆記本是 8 核 IntelCPU卢鹦。
清單 7. 設(shè)置為 8 個(gè)線程后 GC 輸出
[GC [ParNew: 8967K->676K(14784K), 0.0036983 secs] 8967K->676K(63936K),
0.0037662 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 14784K, used 9395K [0x040e0000, 0x050e0000, 0x050e0000)
eden space 13184K, 66% used [0x040e0000, 0x04963e58, 0x04dc0000)
from space 1600K, 42% used [0x04f50000, 0x04ff9100, 0x050e0000)
to space 1600K, 0% used [0x04dc0000, 0x04dc0000, 0x04f50000)
concurrent mark-sweep generation total 49152K, used 0K [0x050e0000, 0x080e0000, 0x0a0e0000)
concurrent-mark-sweep perm gen total 12288K, used 2060K [0x0a0e0000, 0x0ace0000, 0x0e0e0000)
清單 8. 設(shè)置為 128 個(gè)線程后 GC 輸出
[GC [ParNew: 8967K->664K(14784K), 0.0207327 secs] 8967K->664K(63936K),
0.0208077 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
清單 9. 設(shè)置為 640 個(gè)線程后 GC 輸出
[GC [ParNew: 8967K->667K(14784K), 0.2323704 secs] 8967K->667K(63936K),
0.2324778 secs] [Times: user=0.34 sys=0.02, real=0.23 secs]
清單 10. 設(shè)置為 1280 個(gè)線程后 GC 輸出
Error occurred during initialization of VM
Too small new size specified
新生代并行回收 (Parallel Scavenge) 收集器
新生代并行回收收集器也是使用復(fù)制算法的收集器劝堪。從表面上看揉稚,它和并行收集器一樣都是多線程窃植、獨(dú)占式的收集器。但是巷怜,并行回收收集器有一個(gè)重要的特點(diǎn):它非常關(guān)注系統(tǒng)的吞吐量暴氏。
新生代并行回收收集器可以使用以下參數(shù)啟用:
-XX:+UseParallelGC:新生代使用并行回收收集器,老年代使用串行收集器关带。
-XX:+UseParallelOldGC:新生代和老年代都是用并行回收收集器沼撕。
清單 11. 設(shè)置為 24 個(gè)線程后 GC 輸出
Heap
PSYoungGen total 4800K, used 893K [0x1dac0000, 0x1e010000, 0x23010000)
eden space 4160K, 21% used [0x1dac0000,0x1db9f570,0x1ded0000)
from space 640K, 0% used [0x1df70000,0x1df70000,0x1e010000)
to space 640K, 0% used [0x1ded0000,0x1ded0000,0x1df70000)
ParOldGen total 19200K, used 16384K [0x13010000, 0x142d0000, 0x1dac0000)
object space 19200K, 85% used [0x13010000,0x14010020,0x142d0000)
PSPermGen total 12288K, used 2054K [0x0f010000, 0x0fc10000, 0x13010000)
object space 12288K, 16% used [0x0f010000,0x0f2119c0,0x0fc10000)
新生代并行回收收集器可以使用以下參數(shù)啟用:
-XX:+MaxGCPauseMills:設(shè)置最大垃圾收集停頓時(shí)間,它的值是一個(gè)大于 0 的整數(shù)磨总。收集器在工作時(shí)會(huì)調(diào)整 Java 堆大小或者其他一些參數(shù),盡可能地把停頓時(shí)間控制在 MaxGCPauseMills 以內(nèi)蚪燕。如果希望減少停頓時(shí)間奔浅,而把這個(gè)值設(shè)置得很小,為了達(dá)到預(yù)期的停頓時(shí)間鲁驶,JVM 可能會(huì)使用一個(gè)較小的堆 (一個(gè)小堆比一個(gè)大堆回收快),而這將導(dǎo)致垃圾回收變得很頻繁灵嫌,從而增加了垃圾回收總時(shí)間葛作,降低了吞吐量赂蠢。
-XX:+GCTimeRatio:設(shè)置吞吐量大小辨泳,它的值是一個(gè) 0-100 之間的整數(shù)玖院。假設(shè) GCTimeRatio 的值為 n难菌,那么系統(tǒng)將花費(fèi)不超過(guò) 1/(1+n) 的時(shí)間用于垃圾收集郊酒。比如 GCTimeRatio 等于 19键袱,則系統(tǒng)用于垃圾收集的時(shí)間不超過(guò) 1/(1+19)=5%蹄咖。默認(rèn)情況下,它的取值是 99蚜迅,即不超過(guò) 1%的時(shí)間用于垃圾收集谁不。
除此之外,并行回收收集器與并行收集器另一個(gè)不同之處在于,它支持一種自適應(yīng)的 GC 調(diào)節(jié)策略馏段,使用-XX:+UseAdaptiveSizePolicy 可以打開自適應(yīng) GC 策略。在這種模式下亡蓉,新生代的大小、eden 和 survivor 的比例、晉升老年代的對(duì)象年齡等參數(shù)會(huì)被自動(dòng)調(diào)整硫麻,以達(dá)到在堆大小拿愧、吞吐量和停頓時(shí)間之間的平衡點(diǎn)。在手工調(diào)優(yōu)比較困難的場(chǎng)合唾戚,可以直接使用這種自適應(yīng)的方式待诅,僅指定虛擬機(jī)的最大堆卑雁、目標(biāo)的吞吐量 (GCTimeRatio) 和停頓時(shí)間 (MaxGCPauseMills)序厉,讓虛擬機(jī)自己完成調(diào)優(yōu)工作弛房。
清單 12. 新生代并行回收收集器 GC 輸出
Heap
PSYoungGen total 4800K, used 893K [0x1dac0000, 0x1e010000, 0x23010000)
eden space 4160K, 21% used [0x1dac0000,0x1db9f570,0x1ded0000)
from space 640K, 0% used [0x1df70000,0x1df70000,0x1e010000)
to space 640K, 0% used [0x1ded0000,0x1ded0000,0x1df70000)
PSOldGen total 19200K, used 16384K [0x13010000, 0x142d0000, 0x1dac0000)
object space 19200K, 85% used [0x13010000,0x14010020,0x142d0000)
PSPermGen total 12288K, used 2054K [0x0f010000, 0x0fc10000, 0x13010000)
object space 12288K, 16% used [0x0f010000,0x0f2119c0,0x0fc10000)
它也顯示了收集器的工作成果文捶,也就是回收前的內(nèi)存大小和回收后的內(nèi)存大小粹排,以及花費(fèi)的時(shí)間顽耳。
老年代并行回收收集器
老年代的并行回收收集器也是一種多線程并發(fā)的收集器射富。和新生代并行回收收集器一樣胰耗,它也是一種關(guān)注吞吐量的收集器。老年代并行回收收集器使用標(biāo)記-壓縮算法卖漫,JDK1.6 之后開始啟用羊始。
使用-XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收收集器速警,這是一對(duì)非常關(guān)注吞吐量的垃圾收集器組合,在對(duì)吞吐量敏感的系統(tǒng)中长豁,可以考慮使用匠襟。參數(shù)-XX:ParallelGCThreads 也可以用于設(shè)置垃圾回收時(shí)的線程數(shù)量酸舍。
清單 13 是設(shè)置線程數(shù)量為 100 時(shí)老年代并行回收收集器輸出日志:
清單 13. 老年代并行回收收集器設(shè)置 100 線程時(shí) GC 輸出
Heap
PSYoungGen total 4800K, used 893K [0x1dac0000, 0x1e010000, 0x23010000)
eden space 4160K, 21% used [0x1dac0000,0x1db9f570,0x1ded0000)
from space 640K, 0% used [0x1df70000,0x1df70000,0x1e010000)
to space 640K, 0% used [0x1ded0000,0x1ded0000,0x1df70000)
ParOldGen total 19200K, used 16384K [0x13010000, 0x142d0000, 0x1dac0000)
object space 19200K, 85% used [0x13010000,0x14010020,0x142d0000)
PSPermGen total 12288K, used 2054K [0x0f010000, 0x0fc10000, 0x13010000)
object space 12288K, 16% used [0x0f010000,0x0f2119c0,0x0fc10000)
CMS 收集器
與并行回收收集器不同啃勉,CMS 收集器主要關(guān)注于系統(tǒng)停頓時(shí)間淮阐。CMS 是 Concurrent Mark Sweep 的縮寫泣特,意為并發(fā)標(biāo)記清除状您,從名稱上可以得知膏孟,它使用的是標(biāo)記-清除算法,同時(shí)它又是一個(gè)使用多線程并發(fā)回收的垃圾收集器颗搂。
CMS 工作時(shí),主要步驟有:初始標(biāo)記先改、并發(fā)標(biāo)記、重新標(biāo)記仇奶、并發(fā)清除和并發(fā)重置。其中初始標(biāo)記和重新標(biāo)記是獨(dú)占系統(tǒng)資源的别惦,而并發(fā)標(biāo)記夫椭、并發(fā)清除和并發(fā)重置是可以和用戶線程一起執(zhí)行的。因此,從整體上來(lái)說(shuō)仁讨,CMS 收集不是獨(dú)占式的盐固,它可以在應(yīng)用程序運(yùn)行過(guò)程中進(jìn)行垃圾回收。
根據(jù)標(biāo)記-清除算法闰挡,初始標(biāo)記长酗、并發(fā)標(biāo)記和重新標(biāo)記都是為了標(biāo)記出需要回收的對(duì)象夺脾。并發(fā)清理則是在標(biāo)記完成后茉继,正式回收垃圾對(duì)象烁竭;并發(fā)重置是指在垃圾回收完成后派撕,重新初始化 CMS 數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù),為下一次垃圾回收做好準(zhǔn)備镀赌。并發(fā)標(biāo)記喉钢、并發(fā)清理和并發(fā)重置都是可以和應(yīng)用程序線程一起執(zhí)行的肠虽。
CMS 收集器在其主要的工作階段雖然沒(méi)有暴力地徹底暫停應(yīng)用程序線程,但是由于它和應(yīng)用程序線程并發(fā)執(zhí)行豹缀,相互搶占 CPU邢笙,所以在 CMS 執(zhí)行期內(nèi)對(duì)應(yīng)用程序吞吐量造成一定影響侍匙。CMS 默認(rèn)啟動(dòng)的線程數(shù)是 (ParallelGCThreads+3)/4),ParallelGCThreads 是新生代并行收集器的線程數(shù),也可以通過(guò)-XX:ParallelCMSThreads 參數(shù)手工設(shè)定 CMS 的線程數(shù)量妇汗。當(dāng) CPU 資源比較緊張時(shí),受到 CMS 收集器線程的影響储狭,應(yīng)用程序的性能在垃圾回收階段可能會(huì)非常糟糕慈参。
由于 CMS 收集器不是獨(dú)占式的回收器驮配,在 CMS 回收過(guò)程中着茸,應(yīng)用程序仍然在不停地工作。在應(yīng)用程序工作過(guò)程中澎语,又會(huì)不斷地產(chǎn)生垃圾。這些新生成的垃圾在當(dāng)前 CMS 回收過(guò)程中是無(wú)法清除的义图。同時(shí),因?yàn)閼?yīng)用程序沒(méi)有中斷奏夫,所以在 CMS 回收過(guò)程中廊谓,還應(yīng)該確保應(yīng)用程序有足夠的內(nèi)存可用麻削。因此,CMS 收集器不會(huì)等待堆內(nèi)存飽和時(shí)才進(jìn)行垃圾回收扫责,而是當(dāng)前堆內(nèi)存使用率達(dá)到某一閾值時(shí)公给,便開始進(jìn)行回收淌铐,以確保應(yīng)用程序在 CMS 工作過(guò)程中依然有足夠的空間支持應(yīng)用程序運(yùn)行腿准。
這個(gè)回收閾值可以使用-XX:CMSInitiatingOccupancyFraction 來(lái)指定,默認(rèn)是 68弟跑。即當(dāng)老年代的空間使用率達(dá)到 68%時(shí)孟辑,會(huì)執(zhí)行一次 CMS 回收。如果應(yīng)用程序的內(nèi)存使用率增長(zhǎng)很快奈嘿,在 CMS 的執(zhí)行過(guò)程中,已經(jīng)出現(xiàn)了內(nèi)存不足的情況衔憨,此時(shí)巫财,CMS 回收將會(huì)失敗悍及,JVM 將啟動(dòng)老年代串行收集器進(jìn)行垃圾回收心赶。如果這樣,應(yīng)用程序?qū)⑼耆袛嗤址钡嚼占瓿上郏@時(shí)蒸健,應(yīng)用程序的停頓時(shí)間可能很長(zhǎng)婉商。因此丈秩,根據(jù)應(yīng)用程序的特點(diǎn)箫攀,可以對(duì)-XX:CMSInitiatingOccupancyFraction 進(jìn)行調(diào)優(yōu)匠童。如果內(nèi)存增長(zhǎng)緩慢塑顺,則可以設(shè)置一個(gè)稍大的值严拒,大的閾值可以有效降低 CMS 的觸發(fā)頻率裤唠,減少老年代回收的次數(shù)可以較為明顯地改善應(yīng)用程序性能种蘸。反之航瞭,如果應(yīng)用程序內(nèi)存使用率增長(zhǎng)很快刊侯,則應(yīng)該降低這個(gè)閾值藕届,以避免頻繁觸發(fā)老年代串行收集器亭饵。
標(biāo)記-清除算法將會(huì)造成大量?jī)?nèi)存碎片,離散的可用空間無(wú)法分配較大的對(duì)象只冻。在這種情況下山橄,即使堆內(nèi)存仍然有較大的剩余空間舍悯,也可能會(huì)被迫進(jìn)行一次垃圾回收,以換取一塊可用的連續(xù)內(nèi)存,這種現(xiàn)象對(duì)系統(tǒng)性能是相當(dāng)不利的观蓄,為了解決這個(gè)問(wèn)題侮穿,CMS 收集器還提供了幾個(gè)用于內(nèi)存壓縮整理的算法。
-XX:+UseCMSCompactAtFullCollection 參數(shù)可以使 CMS 在垃圾收集完成后狗准,進(jìn)行一次內(nèi)存碎片整理。內(nèi)存碎片的整理并不是并發(fā)進(jìn)行的饼酿。-XX:CMSFullGCsBeforeCompaction 參數(shù)可以用于設(shè)定進(jìn)行多少次 CMS 回收后故俐,進(jìn)行一次內(nèi)存壓縮药版。
-XX:CMSInitiatingOccupancyFraction 設(shè)置為 100槽片,同時(shí)設(shè)置-XX:+UseCMSCompactAtFullCollection 和-XX:CMSFullGCsBeforeCompaction肢础,日志輸出如下:
清單 14.CMS 垃圾回收器 GC 輸出
[GC [DefNew: 825K->149K(4928K), 0.0023384 secs][Tenured: 8704K->661K(10944K),
0.0587725 secs] 9017K->661K(15872K),
[Perm : 374K->374K(12288K)], 0.0612037 secs] [Times: user=0.01 sys=0.02, real=0.06 secs]
Heap
def new generation total 4992K, used 179K [0x27010000, 0x27570000, 0x2c560000)
eden space 4480K, 4% used [0x27010000, 0x2703cda8, 0x27470000)
from space 512K, 0% used [0x27470000, 0x27470000, 0x274f0000)
to space 512K, 0% used [0x274f0000, 0x274f0000, 0x27570000)
tenured generation total 10944K, used 8853K [0x2c560000, 0x2d010000, 0x37010000)
the space 10944K, 80% used [0x2c560000, 0x2ce057c8, 0x2ce05800, 0x2d010000)
compacting perm gen total 12288K, used 374K [0x37010000, 0x37c10000, 0x3b010000)
the space 12288K, 3% used [0x37010000, 0x3706db10, 0x3706dc00, 0x37c10000)
ro space 10240K, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)
rw space 12288K, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
G1 收集器 (Garbage First)
G1 收集器的目標(biāo)是作為一款服務(wù)器的垃圾收集器剩盒,因此堪伍,它在吞吐量和停頓控制上跟匆,預(yù)期要優(yōu)于 CMS 收集器玛臂。
與 CMS 收集器相比垢揩,G1 收集器是基于標(biāo)記-壓縮算法的叁巨。因此蚀瘸,它不會(huì)產(chǎn)生空間碎片庶橱,也沒(méi)有必要在收集完成后,進(jìn)行一次獨(dú)占式的碎片整理工作枫绅。G1 收集器還可以進(jìn)行非常精確的停頓控制并淋。它可以讓開發(fā)人員指定當(dāng)停頓時(shí)長(zhǎng)為 M 時(shí)县耽,垃圾回收時(shí)間不超過(guò) N唾琼。使用參數(shù)-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 來(lái)啟用 G1 回收器父叙,設(shè)置 G1 回收器的目標(biāo)停頓時(shí)間:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200涌乳。
收集器對(duì)系統(tǒng)性能的影響
通過(guò)清單 15 所示代碼運(yùn)行 1 萬(wàn)次循環(huán),每次分配 512*100B 空間悠咱,采用不同的垃圾回收器析既,輸出程序運(yùn)行所消耗的時(shí)間。
清單 15. 性能測(cè)試代碼
import java.util.HashMap;
public class GCTimeTest {
static HashMap map = new HashMap();
public static void main(String[] args){
long begintime = System.currentTimeMillis();
for(int i=0;i<10000;i++){
if(map.size()*512/1024/1024>=400){
map.clear();//保護(hù)內(nèi)存不溢出
System.out.println("clean map");
}
byte[] b1;
for(int j=0;j<100;j++){
b1 = new byte[512];
map.put(System.nanoTime(), b1);//不斷消耗內(nèi)存
}
}
long endtime = System.currentTimeMillis();
System.out.println(endtime-begintime);
}
}
使用參數(shù)-Xmx512M -Xms512M -XX:+UseParNewGC 運(yùn)行代碼宰译,輸出如下:
clean map 8565
cost time=1655
使用參數(shù)-Xmx512M -Xms512M -XX:+UseParallelOldGC –XX:ParallelGCThreads=8 運(yùn)行代碼,輸出如下:
clean map 8798
cost time=1998
使用參數(shù)-Xmx512M -Xms512M -XX:+UseSerialGC 運(yùn)行代碼缀拭,輸出如下:
clean map 8864
cost time=1717
使用參數(shù)-Xmx512M -Xms512M -XX:+UseConcMarkSweepGC 運(yùn)行代碼诲泌,輸出如下:
clean map 8862
cost time=1530
上面例子的 GC 輸出可以看出,采用不同的垃圾回收機(jī)制及設(shè)定不同的線程數(shù)诚卸,對(duì)于代碼段的整體執(zhí)行時(shí)間有較大的影響合溺。需要讀者有針對(duì)性地選用適合自己代碼段的垃圾回收機(jī)制棠赛。
GC 相關(guān)參數(shù)總結(jié)
1. 與串行回收器相關(guān)的參數(shù)
-XX:+UseSerialGC:在新生代和老年代使用串行回收器。
-XX:+SuivivorRatio:設(shè)置 eden 區(qū)大小和 survivor 區(qū)大小的比例。
-XX:+PretenureSizeThreshold:設(shè)置大對(duì)象直接進(jìn)入老年代的閾值贸伐。當(dāng)對(duì)象的大小超過(guò)這個(gè)值時(shí),將直接在老年代分配商膊。
-XX:MaxTenuringThreshold:設(shè)置對(duì)象進(jìn)入老年代的年齡的最大值晕拆。每一次 Minor GC 后,對(duì)象年齡就加 1茬缩。任何大于這個(gè)年齡的對(duì)象凰锡,一定會(huì)進(jìn)入老年代掂为。
2. 與并行 GC 相關(guān)的參數(shù)
-XX:+UseParNewGC: 在新生代使用并行收集器昼扛。
-XX:+UseParallelOldGC: 老年代使用并行回收收集器欲诺。
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)。通常情況下可以和 CPU 數(shù)量相等塞颁。但在 CPU 數(shù)量比較多的情況下,設(shè)置相對(duì)較小的數(shù)值也是合理的咽安。
-XX:MaxGCPauseMills:設(shè)置最大垃圾收集停頓時(shí)間。它的值是一個(gè)大于 0 的整數(shù)募逞。收集器在工作時(shí)放接,會(huì)調(diào)整 Java 堆大小或者其他一些參數(shù),盡可能地把停頓時(shí)間控制在 MaxGCPauseMills 以內(nèi)蜕青。
-XX:GCTimeRatio:設(shè)置吞吐量大小右核,它的值是一個(gè) 0-100 之間的整數(shù)菱鸥。假設(shè) GCTimeRatio 的值為 n,那么系統(tǒng)將花費(fèi)不超過(guò) 1/(1+n) 的時(shí)間用于垃圾收集染苛。
-XX:+UseAdaptiveSizePolicy:打開自適應(yīng) GC 策略茶行。在這種模式下怔鳖,新生代的大小,eden 和 survivor 的比例度陆、晉升老年代的對(duì)象年齡等參數(shù)會(huì)被自動(dòng)調(diào)整懂傀,以達(dá)到在堆大小恃泪、吞吐量和停頓時(shí)間之間的平衡點(diǎn)贝乎。
3. 與 CMS 回收器相關(guān)的參數(shù)
-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器虫几。
-XX:+ParallelCMSThreads: 設(shè)定 CMS 的線程數(shù)量辆脸。
-XX:+CMSInitiatingOccupancyFraction:設(shè)置 CMS 收集器在老年代空間被使用多少后觸發(fā)每强,默認(rèn)為 68%。
-XX:+UseFullGCsBeforeCompaction:設(shè)定進(jìn)行多少次 CMS 垃圾回收后辨绊,進(jìn)行一次內(nèi)存壓縮门坷。
-XX:+CMSClassUnloadingEnabled:允許對(duì)類元數(shù)據(jù)進(jìn)行回收默蚌。
-XX:+CMSParallelRemarkEndable:啟用并行重標(biāo)記鼻弧。
-XX:CMSInitatingPermOccupancyFraction:當(dāng)永久區(qū)占用率達(dá)到這一百分比后攘轩,啟動(dòng) CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)度帮。
-XX:UseCMSInitatingOccupancyOnly:表示只在到達(dá)閾值的時(shí)候笨篷,才進(jìn)行 CMS 回收冕屯。
-XX:+CMSIncrementalMode:使用增量模式安聘,比較適合單 CPU浴韭。
4. 與 G1 回收器相關(guān)的參數(shù)
-XX:+UseG1GC:使用 G1 回收器念颈。
-XX:+UnlockExperimentalVMOptions:允許使用實(shí)驗(yàn)性參數(shù)。
-XX:+MaxGCPauseMills:設(shè)置最大垃圾收集停頓時(shí)間窟感。
-XX:+GCPauseIntervalMills:設(shè)置停頓間隔時(shí)間柿祈。
5. 其他參數(shù)
-XX:+DisableExplicitGC: 禁用顯示 GC蜜自。
結(jié)束語(yǔ)
通過(guò)本文的學(xué)習(xí)重荠,讀者可以掌握基本的 JVM 垃圾回收器設(shè)計(jì)原理及使用規(guī)范尾膊。基于筆者多年的工作經(jīng)驗(yàn)待笑,沒(méi)有哪一條優(yōu)化是可以照本宣科的寞缝,它一定是基于您對(duì) JVM 垃圾回收器工作原理及自身程序設(shè)計(jì)有一定了解前提下,通過(guò)大量的實(shí)驗(yàn)來(lái)找出最適合自己的優(yōu)化方案集侯。