JVM垃圾回收器原理及使用介紹
引用計數(shù)法(Reference Counting)
分區(qū)算法(partitioning algorithm)
新生代并行回收 (Parallel Scavenge) 收集器
Java語音的一大特點就是可以進行自動垃圾回收處理挣郭,而無需開發(fā)人員過于關(guān)注系統(tǒng)資源捂龄,例如內(nèi)存資源的釋放情況佛纫。自動垃圾收集雖然大大減輕了開發(fā)人員的工作量凶硅,但是也增加了軟件系統(tǒng)的負擔(dān)悠咱。
引用計數(shù)法(Reference Counting)
引用計數(shù)器在微軟的COM組件技術(shù)中、Adobe的ActionScript3種都有使用贾虽。
引用計數(shù)器的實現(xiàn)很簡單,對于一個對象A吼鱼,只要有任何一個對象引用了A蓬豁,則A的引用計數(shù)器就加1,當(dāng)引用失效時菇肃,引用計數(shù)器就減1.只要對象A的引用計數(shù)器的值為0地粪,則對象A就不可能再被使用。
引用計數(shù)器的實現(xiàn)也非常簡單琐谤,只需要為每個對象配置一個整形的計數(shù)器即可蟆技。但是引用計數(shù)器有一個嚴重的問題,既無法處理循環(huán)引用的情況斗忌。因此质礼,在Java的垃圾回收器中沒有使用這種算法。
一個簡單的循環(huán)引用問題描述如下 : 有對象A和對象B织阳,對象A中含有對象B的引用眶蕉,對象B中含有對象A的引用。此時唧躲,對象A和對象B的引用計數(shù)器都不為0.但是在系統(tǒng)中卻不存在任何第三個對象引用了A或B造挽。也就是說碱璃,A和
B是應(yīng)該被回收的垃圾對象,但由于垃圾對象間互相引用饭入,從而使垃圾回收器無法識別嵌器,引起內(nèi)存泄漏。
標記-清除算法將垃圾回收分為兩個階段 : 標記階段和清除階段谐丢。一種可行的實現(xiàn)是爽航,在標記階段首先通過根節(jié)點,標記所有從根節(jié)點開始的較大對象庇谆。因此岳掐,未被標記的對象就是未被引用的垃圾對象。然后饭耳,在清除階段串述,清除所有未被標記的對象。該算法最大的問題是存在大量的空間碎片寞肖,因為回收后的空間是不連續(xù)的纲酗。在對象的堆空間分配過程中,尤其是大對象的內(nèi)存分配新蟆,不連續(xù)的內(nèi)存空間的工作效率要低于連續(xù)的空間觅赊。
將現(xiàn)有的內(nèi)存空間分為兩塊,每次只使用其中一塊琼稻,在垃圾回收時將正在使用的內(nèi)存中的存活對象復(fù)制到未被使用的內(nèi)存中吮螺,之后,清除正在使用的內(nèi)存卡中的所有對象帕翻,交換兩個內(nèi)存的角色鸠补,完成垃圾回收。
如果系統(tǒng)中的垃圾對象很多嘀掸,復(fù)制算法需要復(fù)制的存儲對象數(shù)量并不會太大紫岩。因此在真正需要垃圾回收的時刻。復(fù)制算法的效率是很高的睬塌。又由于對象在垃圾回收過程中統(tǒng)一被復(fù)制到新的內(nèi)存空間中泉蝌,因此,可確笨纾回收后的內(nèi)存空間是沒有碎片的勋陪。改算法的缺點是將系統(tǒng)內(nèi)存折半。
Java的新生代串行垃圾回收器中使用了復(fù)制算法的思想硫兰。新生代分為eden空間粥鞋、from空間、to空間3個部分瞄崇。其中呻粹,from空間和to空間可以視為用于復(fù)制的兩塊大小相同壕曼、地位相等,且可進行角色互換的空間塊等浊。from空間和to空間也稱之為survivor空間腮郊,既幸存者空間,用于存放未被回收的對象筹燕。
在垃圾回收時轧飞,eden空間中的存活對象被復(fù)制到未使用的survivor空間中(假設(shè)是to),正在使用的survivor空間(假設(shè)是from)中的年輕對象也會被復(fù)制到to空間中(大對象撒踪,或者老年對象會直接進入老年代过咬,如果to空間已滿,則對象也會直接進入老年代)制妄。此時掸绞,eden空間和from空間中的剩余對象就是垃圾對象,可以直接清空耕捞,to空間則存放此回收后的存活對象衔掸。這種改進的復(fù)制算法既保證了空間的連續(xù)性,又避免了大量的內(nèi)存空間浪費俺抽。
復(fù)制算法的高效性是建立在存活對象少敞映、垃圾對象多的前提下的。這種情況在年輕代經(jīng)常發(fā)生磷斧,但是在老年代更常見的情況是大部分對象都是存活對象振愿。如果依然使用復(fù)制算法,由于存活的對象較多弛饭,復(fù)制的成本也將很高埃疫。
標記-壓縮算法是一種老年代的回收算法,它在標記-清除算法的基礎(chǔ)上做了一些優(yōu)化孩哑。也首先需要從根節(jié)點開始懟所有可達對象做一次標記,但之后翠桦,它并不簡單地清理未標記的對象横蜒,而是將所有的存活對象壓縮到內(nèi)存的一端。之后销凑,清理邊界外所有的空間丛晌。這種方法既避免了碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間斗幼,因此澎蛛,其性價比比較高。
在垃圾回收過程中蜕窿,應(yīng)用軟件將處于一種CPU消耗很高的狀態(tài)谋逻。在這種CPU消耗很高的狀態(tài)下呆馁,應(yīng)用程序所有的線程都會掛起,暫停一切正常的工作毁兆,等待垃圾回收的完成浙滤。如果垃圾回收時間過長,應(yīng)用程序會被掛起很久气堕,將嚴重影響用戶體驗或者系統(tǒng)的穩(wěn)定性纺腊。
增量算法的基本思想是,如果一次性將所有的垃圾進行處理茎芭,需要造成系統(tǒng)長時間的停頓意蛀,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替執(zhí)行涝缝。每次,垃圾收集線程只收集一小片區(qū)域的內(nèi)存空間,接著切換到應(yīng)用程序線程蠢沿。依次反復(fù),直到垃圾收集完成扣典。使用這種方式懂傀,由于在垃圾回收過程中,間斷性地還執(zhí)行了應(yīng)用程序代碼犀呼,所以能減少系統(tǒng)的停頓時間幸撕。但是,因為線程切換和上下文轉(zhuǎn)換的消耗外臂,會使得垃圾回收的總體成本上升坐儿,造成系統(tǒng)吞吐量的下降。
分區(qū)算法(partitioning algorithm)
這種方法將整個空間劃分成連續(xù)的不同的小區(qū)間宋光,每個區(qū)間都獨立使用貌矿,獨立回收,好處是可以控制一次回收多少個小區(qū)間罪佳。
根據(jù)垃圾回收對象的特性逛漫,不同階段最優(yōu)的方式是使用合適的算法用于本階段的垃圾回收,分代算法既是基于這種思想赘艳,它將內(nèi)存區(qū)間根據(jù)對象的特點分成幾塊酌毡,根據(jù)每塊內(nèi)存區(qū)間的特點,使用不同的回收算法蕾管,以提高垃圾回收效率枷踏。以Hot Spot虛擬機為例,它將所有的新建對象都放入稱為年輕代的內(nèi)存區(qū)域掰曾,年輕代的特點是對象會很快回收旭蠕,因此,在年輕代就選擇效率較高的復(fù)制算法。當(dāng)一個對象經(jīng)過幾次回收后依然存活掏熬,對象就會被放入稱為老生代的內(nèi)存空間佑稠。在老生代中,幾乎所有的對象都是經(jīng)過幾次垃圾回收后依然得以幸存的孽江。因此讶坯,可以認為這些對象在一段時期內(nèi),甚至在應(yīng)用程序的整個生命周期中岗屏,將是常駐內(nèi)存的辆琅。如果依然使用復(fù)制算法回收老生代,將需要復(fù)制大量對象这刷。再加上老生代的回收性價比也要低于新生代婉烟,因此這種做法也是不可取的。根據(jù)分代的思想暇屋,可以對老年代的回收使用與新生代不同的標記-壓縮算法似袁,以提高垃圾回收效率。
按線程數(shù)分,可以分為串行垃圾回收器和并行垃圾回收器定鸟。串行垃圾回收器一次只使用一個線程進行垃圾回收而涉;并行垃圾回收器一次將開啟多個線程同時進行垃圾回收。在并行能力較強的CPU上联予,使用并行垃圾回收器可以縮短GC的停頓時間啼县。
按照工作模式分,可以分為并發(fā)式垃圾回收器和獨占式垃圾回收器沸久。并發(fā)式垃圾回收器與應(yīng)用程序線程交替工作季眷,以盡可能減少應(yīng)用程序的停頓時間;獨占式垃圾回收器(stop the world)一旦運行卷胯,就停止應(yīng)用程序中的其他所有線程子刮,直到垃圾回收過程完全結(jié)束。
按碎片處理方式可分為壓縮式垃圾回收器和非壓縮式垃圾回收器窑睁。壓縮式垃圾回收器會在回收完成后挺峡,對存活對象進行壓縮整理,消除回收后的碎片卵慰;非壓縮式的垃圾回收器不進行這步操作。
按工作的內(nèi)存區(qū)間佛呻,又可分為新生代垃圾回收器和老年代垃圾回收器裳朋。
吞吐量 :指在應(yīng)用程序的生命周期內(nèi),應(yīng)用程序所花費的時間和系統(tǒng)總運行時間的比值鲤嫡。系統(tǒng)總運行時間 = 應(yīng)用程序耗時 + GC耗時送挑。如果系統(tǒng)運行了100main,GC耗時1min暖眼,那么系統(tǒng)的吞吐量就是(100-1)/ 100 = 99%惕耕。
垃圾回收器負載 :和吞吐量相反,垃圾回收器負載指來記回收器耗時與系統(tǒng)運行總時間的比值诫肠。
停頓時間 :指垃圾回收器正在運行時司澎,應(yīng)用程序的暫停時間。對于獨占回收器而言栋豫,停頓時間可能會比較長挤安。使用并發(fā)的回收器時,由于垃圾回收器和應(yīng)用程序交替運行丧鸯,程序的停頓時間會變短蛤铜,但是,由于其效率很可能不如獨占垃圾回收器丛肢,故系統(tǒng)的吞吐量可能會較低围肥。
垃圾回收頻率 :指垃圾回收器多長時間會運行一次。一般來說蜂怎,對于固定的應(yīng)用而言穆刻,垃圾回收器的頻率應(yīng)該是越低越好。通常大堆空間可以有效降低垃圾回收發(fā)生的頻率派敷,但是可能會增加回收產(chǎn)生的停頓時間蛹批。
反應(yīng)時間 :應(yīng)當(dāng)一個對象被稱為垃圾后多長時間內(nèi),它所占據(jù)的內(nèi)存空間會被釋放篮愉。
堆分配 :不同的垃圾回收器對堆內(nèi)存的分配方式可能是不同的腐芍。一個良好的垃圾收集器應(yīng)該有一個合理的堆內(nèi)存區(qū)間劃分。
串行收集器主要有兩個特點 : 第一试躏,它僅僅使用單線程進行垃圾回收猪勇;第二,它獨占式的垃圾回收颠蕴。
在串行收集器進行垃圾回收時泣刹,Java應(yīng)用程序中的線程都需要暫停,等待垃圾回收的完成犀被,這樣給用戶體驗造成較差效果椅您。雖然如此,串行收集器卻是一個成熟寡键、經(jīng)過長時間生成環(huán)境考驗的極為高效的收集器掀泳。新生代串行處理器使用復(fù)制算法,實現(xiàn)相對簡單,邏輯處理特別高效员舵,且沒有線程切換的開銷脑沿。在諸如單CPU處理器或者較小的應(yīng)用內(nèi)存等硬件平臺不是特別優(yōu)越的場合,它的性能表現(xiàn)可以超過并行回收器和并發(fā)回收器马僻。在HotSport虛擬機中庄拇,使用-XX:+UseSerialGC參數(shù)可以指定使用新生代串行收集器和老年代串行收集器。當(dāng)JVM在Client模式下運行時韭邓,它是默認的垃圾收集器措近。一次新生代串行收集器的工作輸入日志類似如清單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)存占用量仍秤,以及垃圾回收所消耗的時間熄诡。
老年代串行收集器使用的是標記-壓縮算法。和新生代串行收集器一樣诗力,它也是一個串行的凰浮、獨占式的垃圾回收器。由于老年代垃圾回收通常會使用比新生代垃圾回收更長的時間苇本,因此袜茧,在堆空間較大的應(yīng)用程序中,一旦老年代串行收集器啟動瓣窄,應(yīng)用程序很可能會因此停頓幾秒甚至更長時間笛厦。雖然如此,老年代串行回收器可以和多種新生代回收器配合使用俺夕,同時它也可以作為 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)存使用量瓣颅。
并行收集器是工作在新生代的垃圾收集器,它只簡單地將串行回收器多線程化譬正。它的回收策略宫补、算法以及參數(shù)和串行回收器一樣僻孝。
并行回收器也是獨占式的回收器,在收集過程中守谓,應(yīng)用程序會全部暫停。但由于并行回收器使用多線程進行垃圾回收您单,因此斋荞,在并發(fā)能力比較強的 CPU 上,它產(chǎn)生的停頓時間要短于串行回收器虐秦,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中平酿,并行回收器的效果不會比串行回收器好,由于多線程的壓力悦陋,它的實際表現(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ù)量可以使用-XX:ParallelGCThreads 參數(shù)指定还绘。一般,最好與 CPU 數(shù)量相當(dāng)栖袋,避免過多的線程數(shù)影響垃圾收集性能拍顷。在默認情況下,當(dāng) CPU 數(shù)量小于 8 個塘幅,ParallelGCThreads 的值等于 CPU 數(shù)量昔案,大于 8 個,ParallelGCThreads 的值等于 3+[5*CPU_Count]/8]电媳。以下測試顯示了筆者筆記本上運行 8 個線程時耗時最短踏揣,本人筆記本是 8 核 IntelCPU。
清單 7. 設(shè)置為 8 個線程后 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 個線程后 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 個線程后 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 個線程后 GC 輸出
Error occurred during initialization of VM
Too small new size specified
新生代并行回收 (Parallel Scavenge) 收集器
新生代并行回收收集器也是使用復(fù)制算法的收集器匆背。從表面上看呼伸,它和并行收集器一樣都是多線程、獨占式的收集器钝尸。但是括享,并行回收收集器有一個重要的特點:它非常關(guān)注系統(tǒng)的吞吐量。
新生代并行回收收集器可以使用以下參數(shù)啟用:
-XX:+UseParallelGC:新生代使用并行回收收集器珍促,老年代使用串行收集器铃辖。
-XX:+UseParallelOldGC:新生代和老年代都是用并行回收收集器。
清單 11. 設(shè)置為 24 個線程后 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è)置最大垃圾收集停頓時間猪叙,它的值是一個大于 0 的整數(shù)娇斩。收集器在工作時會調(diào)整 Java 堆大小或者其他一些參數(shù)仁卷,盡可能地把停頓時間控制在 MaxGCPauseMills 以內(nèi)。如果希望減少停頓時間犬第,而把這個值設(shè)置得很小锦积,為了達到預(yù)期的停頓時間,JVM 可能會使用一個較小的堆 (一個小堆比一個大堆回收快)歉嗓,而這將導(dǎo)致垃圾回收變得很頻繁丰介,從而增加了垃圾回收總時間,降低了吞吐量鉴分。
-XX:+GCTimeRatio:設(shè)置吞吐量大小哮幢,它的值是一個 0-100 之間的整數(shù)。假設(shè) GCTimeRatio 的值為 n志珍,那么系統(tǒng)將花費不超過 1/(1+n) 的時間用于垃圾收集橙垢。比如 GCTimeRatio 等于 19,則系統(tǒng)用于垃圾收集的時間不超過 1/(1+19)=5%伦糯。默認情況下柜某,它的取值是 99,即不超過 1%的時間用于垃圾收集敛纲。
除此之外莺琳,并行回收收集器與并行收集器另一個不同之處在于,它支持一種自適應(yīng)的 GC 調(diào)節(jié)策略载慈,使用-XX:+UseAdaptiveSizePolicy 可以打開自適應(yīng) GC 策略惭等。在這種模式下,新生代的大小办铡、eden 和 survivor 的比例辞做、晉升老年代的對象年齡等參數(shù)會被自動調(diào)整,以達到在堆大小寡具、吞吐量和停頓時間之間的平衡點秤茅。在手工調(diào)優(yōu)比較困難的場合,可以直接使用這種自適應(yīng)的方式童叠,僅指定虛擬機的最大堆框喳、目標的吞吐量 (GCTimeRatio) 和停頓時間 (MaxGCPauseMills),讓虛擬機自己完成調(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ā)的收集器放仗。和新生代并行回收收集器一樣,它也是一種關(guān)注吞吐量的收集器撬碟。老年代并行回收收集器使用標記-壓縮算法诞挨,JDK1.6 之后開始啟用莉撇。
使用-XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收收集器,這是一對非常關(guān)注吞吐量的垃圾收集器組合惶傻,在對吞吐量敏感的系統(tǒng)中棍郎,可以考慮使用。參數(shù)-XX:ParallelGCThreads 也可以用于設(shè)置垃圾回收時的線程數(shù)量银室。
清單 13 是設(shè)置線程數(shù)量為 100 時老年代并行回收收集器輸出日志:
清單 13. 老年代并行回收收集器設(shè)置 100 線程時 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 收集器主要關(guān)注于系統(tǒng)停頓時間。CMS 是 Concurrent Mark Sweep 的縮寫粮揉,意為并發(fā)標記清除,從名稱上可以得知抚笔,它使用的是標記-清除算法扶认,同時它又是一個使用多線程并發(fā)回收的垃圾收集器。
CMS 工作時殊橙,主要步驟有:初始標記辐宾、并發(fā)標記、重新標記膨蛮、并發(fā)清除和并發(fā)重置叠纹。其中初始標記和重新標記是獨占系統(tǒng)資源的,而并發(fā)標記敞葛、并發(fā)清除和并發(fā)重置是可以和用戶線程一起執(zhí)行的誉察。因此,從整體上來說惹谐,CMS 收集不是獨占式的持偏,它可以在應(yīng)用程序運行過程中進行垃圾回收。
根據(jù)標記-清除算法氨肌,初始標記鸿秆、并發(fā)標記和重新標記都是為了標記出需要回收的對象。并發(fā)清理則是在標記完成后怎囚,正式回收垃圾對象卿叽;并發(fā)重置是指在垃圾回收完成后,重新初始化 CMS 數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)恳守,為下一次垃圾回收做好準備考婴。并發(fā)標記、并發(fā)清理和并發(fā)重置都是可以和應(yīng)用程序線程一起執(zhí)行的催烘。
CMS 收集器在其主要的工作階段雖然沒有暴力地徹底暫停應(yīng)用程序線程蕉扮,但是由于它和應(yīng)用程序線程并發(fā)執(zhí)行,相互搶占 CPU颗圣,所以在 CMS 執(zhí)行期內(nèi)對應(yīng)用程序吞吐量造成一定影響喳钟。CMS 默認啟動的線程數(shù)是 (ParallelGCThreads+3)/4),ParallelGCThreads 是新生代并行收集器的線程數(shù)屁使,也可以通過-XX:ParallelCMSThreads 參數(shù)手工設(shè)定 CMS 的線程數(shù)量。當(dāng) CPU 資源比較緊張時奔则,受到 CMS 收集器線程的影響蛮寂,應(yīng)用程序的性能在垃圾回收階段可能會非常糟糕。
由于 CMS 收集器不是獨占式的回收器易茬,在 CMS 回收過程中酬蹋,應(yīng)用程序仍然在不停地工作。在應(yīng)用程序工作過程中抽莱,又會不斷地產(chǎn)生垃圾范抓。這些新生成的垃圾在當(dāng)前 CMS 回收過程中是無法清除的。同時食铐,因為應(yīng)用程序沒有中斷匕垫,所以在 CMS 回收過程中,還應(yīng)該確保應(yīng)用程序有足夠的內(nèi)存可用虐呻。因此象泵,CMS 收集器不會等待堆內(nèi)存飽和時才進行垃圾回收,而是當(dāng)前堆內(nèi)存使用率達到某一閾值時斟叼,便開始進行回收偶惠,以確保應(yīng)用程序在 CMS 工作過程中依然有足夠的空間支持應(yīng)用程序運行。
這個回收閾值可以使用-XX:CMSInitiatingOccupancyFraction 來指定朗涩,默認是 68忽孽。即當(dāng)老年代的空間使用率達到 68%時,會執(zhí)行一次 CMS 回收谢床。如果應(yīng)用程序的內(nèi)存使用率增長很快扒腕,在 CMS 的執(zhí)行過程中,已經(jīng)出現(xiàn)了內(nèi)存不足的情況萤悴,此時瘾腰,CMS 回收將會失敗,JVM 將啟動老年代串行收集器進行垃圾回收覆履。如果這樣蹋盆,應(yīng)用程序?qū)⑼耆袛啵钡嚼占瓿上跞@時栖雾,應(yīng)用程序的停頓時間可能很長。因此伟众,根據(jù)應(yīng)用程序的特點析藕,可以對-XX:CMSInitiatingOccupancyFraction 進行調(diào)優(yōu)。如果內(nèi)存增長緩慢凳厢,則可以設(shè)置一個稍大的值账胧,大的閾值可以有效降低 CMS 的觸發(fā)頻率竞慢,減少老年代回收的次數(shù)可以較為明顯地改善應(yīng)用程序性能。反之治泥,如果應(yīng)用程序內(nèi)存使用率增長很快筹煮,則應(yīng)該降低這個閾值,以避免頻繁觸發(fā)老年代串行收集器居夹。
標記-清除算法將會造成大量內(nèi)存碎片败潦,離散的可用空間無法分配較大的對象。在這種情況下准脂,即使堆內(nèi)存仍然有較大的剩余空間劫扒,也可能會被迫進行一次垃圾回收,以換取一塊可用的連續(xù)內(nèi)存狸膏,這種現(xiàn)象對系統(tǒng)性能是相當(dāng)不利的沟饥,為了解決這個問題,CMS 收集器還提供了幾個用于內(nèi)存壓縮整理的算法环戈。
-XX:+UseCMSCompactAtFullCollection 參數(shù)可以使 CMS 在垃圾收集完成后,進行一次內(nèi)存碎片整理澎灸。內(nèi)存碎片的整理并不是并發(fā)進行的院塞。-XX:CMSFullGCsBeforeCompaction 參數(shù)可以用于設(shè)定進行多少次 CMS 回收后,進行一次內(nèi)存壓縮性昭。
-XX:CMSInitiatingOccupancyFraction 設(shè)置為 100拦止,同時設(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 收集器的目標是作為一款服務(wù)器的垃圾收集器糜颠,因此汹族,它在吞吐量和停頓控制上,預(yù)期要優(yōu)于 CMS 收集器其兴。
與 CMS 收集器相比顶瞒,G1 收集器是基于標記-壓縮算法的。因此元旬,它不會產(chǎn)生空間碎片榴徐,也沒有必要在收集完成后,進行一次獨占式的碎片整理工作匀归。G1 收集器還可以進行非常精確的停頓控制坑资。它可以讓開發(fā)人員指定當(dāng)停頓時長為 M 時,垃圾回收時間不超過 N穆端。使用參數(shù)-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 來啟用 G1 回收器袱贮,設(shè)置 G1 回收器的目標停頓時間:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200。
通過清單 15 所示代碼運行 1 萬次循環(huán)体啰,每次分配 512*100B 空間攒巍,采用不同的垃圾回收器嗽仪,輸出程序運行所消耗的時間。
清單 15. 性能測試代碼
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();//保護內(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 運行代碼窑业,輸出如下:
clean map 8565
cost time=1655
使用參數(shù)-Xmx512M -Xms512M -XX:+UseParallelOldGC –XX:ParallelGCThreads=8 運行代碼钦幔,輸出如下:
clean map 8798
cost time=1998
使用參數(shù)-Xmx512M -Xms512M -XX:+UseSerialGC 運行代碼,輸出如下:
clean map 8864
cost time=1717
使用參數(shù)-Xmx512M -Xms512M -XX:+UseConcMarkSweepGC 運行代碼常柄,輸出如下:
clean map 8862
cost time=1530
上面例子的 GC 輸出可以看出鲤氢,采用不同的垃圾回收機制及設(shè)定不同的線程數(shù),對于代碼段的整體執(zhí)行時間有較大的影響西潘。需要讀者有針對性地選用適合自己代碼段的垃圾回收機制卷玉。
與串行回收器相關(guān)的參數(shù)
-XX:+UseSerialGC:在新生代和老年代使用串行回收器。
-XX:+SurvivorRatio:設(shè)置 eden 區(qū)大小和 survivor 區(qū)大小的比例喷市。
-XX:+PretenureSizeThreshold:設(shè)置大對象直接進入老年代的閾值相种。當(dāng)對象的大小超過這個值時,將直接在老年代分配品姓。
-XX:MaxTenuringThreshold:設(shè)置對象進入老年代的年齡的最大值寝并。每一次 Minor GC 后,對象年齡就加 1腹备。任何大于這個年齡的對象衬潦,一定會進入老年代。
與并行 GC 相關(guān)的參數(shù)
-XX:+UseParNewGC: 在新生代使用并行收集器植酥。
-XX:+UseParallelOldGC: 老年代使用并行回收收集器镀岛。
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)。通常情況下可以和 CPU 數(shù)量相等友驮。但在 CPU 數(shù)量比較多的情況下漂羊,設(shè)置相對較小的數(shù)值也是合理的。
-XX:MaxGCPauseMills:設(shè)置最大垃圾收集停頓時間卸留。它的值是一個大于 0 的整數(shù)走越。收集器在工作時,會調(diào)整 Java 堆大小或者其他一些參數(shù)耻瑟,盡可能地把停頓時間控制在 MaxGCPauseMills 以內(nèi)买喧。
-XX:GCTimeRatio:設(shè)置吞吐量大小,它的值是一個 0-100 之間的整數(shù)匆赃。假設(shè) GCTimeRatio 的值為 n淤毛,那么系統(tǒng)將花費不超過 1/(1+n) 的時間用于垃圾收集。
-XX:+UseAdaptiveSizePolicy:打開自適應(yīng) GC 策略算柳。在這種模式下低淡,新生代的大小,eden 和 survivor 的比例、晉升老年代的對象年齡等參數(shù)會被自動調(diào)整蔗蹋,以達到在堆大小何荚、吞吐量和停頓時間之間的平衡點。
與 CMS 回收器相關(guān)的參數(shù)
-XX:+UseConcMarkSweepGC: 新生代使用并行收集器猪杭,老年代使用 CMS+串行收集器餐塘。
-XX:+ParallelCMSThreads: 設(shè)定 CMS 的線程數(shù)量。
-XX:+CMSInitiatingOccupancyFraction:設(shè)置 CMS 收集器在老年代空間被使用多少后觸發(fā)皂吮,默認為 68%戒傻。
-XX:+UseFullGCsBeforeCompaction:設(shè)定進行多少次 CMS 垃圾回收后,進行一次內(nèi)存壓縮蜂筹。
-XX:+CMSClassUnloadingEnabled:允許對類元數(shù)據(jù)進行回收需纳。
-XX:+CMSParallelRemarkEndable:啟用并行重標記。
-XX:CMSInitatingPermOccupancyFraction:當(dāng)永久區(qū)占用率達到這一百分比后艺挪,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)不翩。
-XX:UseCMSInitatingOccupancyOnly:表示只在到達閾值的時候,才進行 CMS 回收麻裳。
-XX:+CMSIncrementalMode:使用增量模式口蝠,比較適合單 CPU。
與 G1 回收器相關(guān)的參數(shù)
-XX:+UseG1GC:使用 G1 回收器津坑。
-XX:+UnlockExperimentalVMOptions:允許使用實驗性參數(shù)妙蔗。
-XX:+MaxGCPauseMills:設(shè)置最大垃圾收集停頓時間。
-XX:+GCPauseIntervalMills:設(shè)置停頓間隔時間国瓮。
其他參數(shù)
-XX:+DisableExplicitGC: 禁用顯示 GC灭必。