垃圾收集基礎
Java 語言的一大特點就是可以進行自動垃圾回收處理,而無需開發(fā)人員過于關注系統(tǒng)資源玄货,例如內存資源的釋放情況渺杉。自動垃圾收集雖然大大減輕了開發(fā)人員的工作量臭猜,但是也增加了軟件系統(tǒng)的負擔。
擁有垃圾收集器可以說是 Java 語言與 C++語言的一項顯著區(qū)別河质。在 C++語言中冀惭,程序員必須小心謹慎地處理每一項內存分配震叙,且內存使用完后必須手工釋放曾經(jīng)占用的內存空間。當內存釋放不夠完全時散休,即存在分配但永不釋放的內存塊媒楼,就會引起內存泄漏,嚴重時甚至導致程序癱瘓戚丸。
以下列舉了垃圾回收器常用的算法及實驗原理:
引用計數(shù)法 (Reference Counting)
引用計數(shù)器在微軟的 COM 組件技術中划址、Adobe 的 ActionScript3 種都有使用。
引用計數(shù)器的實現(xiàn)很簡單限府,對于一個對象 A夺颤,只要有任何一個對象引用了 A,則 A 的引用計數(shù)器就加 1胁勺,當引用失效時世澜,引用計數(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)中卻不存在任何第 3 個對象引用了 A 或 B。也就是說车要,A 和 B 是應該被回收的垃圾對象允粤,但由于垃圾對象間相互引用,從而使垃圾回收器無法識別翼岁,引起內存泄漏类垫。
標記-清除算法 (Mark-Sweep)
標記-清除算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現(xiàn)是琅坡,在標記階段首先通過根節(jié)點悉患,標記所有從根節(jié)點開始的較大對象。因此榆俺,未被標記的對象就是未被引用的垃圾對象售躁。然后坞淮,在清除階段,清除所有未被標記的對象陪捷。該算法最大的問題是存在大量的空間碎片回窘,因為回收后的空間是不連續(xù)的。在對象的堆空間分配過程中揩局,尤其是大對象的內存分配毫玖,不連續(xù)的內存空間的工作效率要低于連續(xù)的空間。
復制算法 (Copying)
將現(xiàn)有的內存空間分為兩快凌盯,每次只使用其中一塊付枫,在垃圾回收時將正在使用的內存中的存活對象復制到未被使用的內存塊中,之后驰怎,清除正在使用的內存塊中的所有對象阐滩,交換兩個內存的角色,完成垃圾回收县忌。
如果系統(tǒng)中的垃圾對象很多掂榔,復制算法需要復制的存活對象數(shù)量并不會太大。因此在真正需要垃圾回收的時刻症杏,復制算法的效率是很高的装获。又由于對象在垃圾回收過程中統(tǒng)一被復制到新的內存空間中,因此厉颤,可確毖ㄔィ回收后的內存空間是沒有碎片的。該算法的缺點是將系統(tǒng)內存折半逼友。
Java 的新生代串行垃圾回收器中使用了復制算法的思想精肃。新生代分為 eden 空間、from 空間帜乞、to 空間 3 個部分司抱。其中 from 空間和 to 空間可以視為用于復制的兩塊大小相同、地位相等黎烈,且可進行角色互換的空間塊习柠。from 和 to 空間也稱為 survivor 空間,即幸存者空間怨喘,用于存放未被回收的對象津畸。
在垃圾回收時,eden 空間中的存活對象會被復制到未使用的 survivor 空間中 (假設是 to)必怜,正在使用的 survivor 空間 (假設是 from) 中的年輕對象也會被復制到 to 空間中 (大對象肉拓,或者老年對象會直接進入老年帶,如果 to 空間已滿梳庆,則對象也會直接進入老年代)暖途。此時卑惜,eden 空間和 from 空間中的剩余對象就是垃圾對象,可以直接清空驻售,to 空間則存放此次回收后的存活對象露久。這種改進的復制算法既保證了空間的連續(xù)性,又避免了大量的內存空間浪費欺栗。
標記-壓縮算法 (Mark-Compact)
復制算法的高效性是建立在存活對象少毫痕、垃圾對象多的前提下的。這種情況在年輕代經(jīng)常發(fā)生迟几,但是在老年代更常見的情況是大部分對象都是存活對象消请。如果依然使用復制算法,由于存活的對象較多类腮,復制的成本也將很高臊泰。
標記-壓縮算法是一種老年代的回收算法,它在標記-清除算法的基礎上做了一些優(yōu)化蚜枢。也首先需要從根節(jié)點開始對所有可達對象做一次標記缸逃,但之后,它并不簡單地清理未標記的對象厂抽,而是將所有的存活對象壓縮到內存的一端需频。之后,清理邊界外所有的空間筷凤。這種方法既避免了碎片的產(chǎn)生贺辰,又不需要兩塊相同的內存空間,因此嵌施,其性價比比較高。
增量算法 (Incremental Collecting)
在垃圾回收過程中莽鸭,應用軟件將處于一種 CPU 消耗很高的狀態(tài)吗伤。在這種 CPU 消耗很高的狀態(tài)下,應用程序所有的線程都會掛起硫眨,暫停一切正常的工作足淆,等待垃圾回收的完成。如果垃圾回收時間過長礁阁,應用程序會被掛起很久巧号,將嚴重影響用戶體驗或者系統(tǒng)的穩(wěn)定性。
增量算法的基本思想是姥闭,如果一次性將所有的垃圾進行處理丹鸿,需要造成系統(tǒng)長時間的停頓,那么就可以讓垃圾收集線程和應用程序線程交替執(zhí)行棚品。每次靠欢,垃圾收集線程只收集一小片區(qū)域的內存空間,接著切換到應用程序線程。依次反復荐操,直到垃圾收集完成票髓。使用這種方式,由于在垃圾回收過程中掷空,間斷性地還執(zhí)行了應用程序代碼肋殴,所以能減少系統(tǒng)的停頓時間。但是坦弟,因為線程切換和上下文轉換的消耗护锤,會使得垃圾回收的總體成本上升,造成系統(tǒng)吞吐量的下降减拭。
分代 (Generational Collecting)
根據(jù)垃圾回收對象的特性蔽豺,不同階段最優(yōu)的方式是使用合適的算法用于本階段的垃圾回收,分代算法即是基于這種思想拧粪,它將內存區(qū)間根據(jù)對象的特點分成幾塊修陡,根據(jù)每塊內存區(qū)間的特點,使用不同的回收算法可霎,以提高垃圾回收的效率魄鸦。以 Hot Spot 虛擬機為例,它將所有的新建對象都放入稱為年輕代的內存區(qū)域癣朗,年輕代的特點是對象會很快回收拾因,因此,在年輕代就選擇效率較高的復制算法旷余。
當一個對象經(jīng)過幾次回收后依然存活绢记,對象就會被放入稱為老生代的內存空間。在老生代中正卧,幾乎所有的對象都是經(jīng)過幾次垃圾回收后依然得以幸存的蠢熄。因此,可以認為這些對象在一段時期內炉旷,甚至在應用程序的整個生命周期中签孔,將是常駐內存的。如果依然使用復制算法回收老生代窘行,將需要復制大量對象饥追。再加上老生代的回收性價比也要低于新生代,因此這種做法也是不可取的罐盔。根據(jù)分代的思想但绕,可以對老年代的回收使用與新生代不同的標記-壓縮算法,以提高垃圾回收效率惶看。
垃圾收集器分類
從不同角度分析垃圾收集器壁熄,可以將其分為不同的類型:
- 按線程數(shù)
可以分為串行垃圾回收器和并行垃圾回收器帚豪。串行垃圾回收器一次只使用一個線程進行垃圾回收;并行垃圾回收器一次將開啟多個線程同時進行垃圾回收草丧。在并行能力較強的 CPU 上狸臣,使用并行垃圾回收器可以縮短 GC 的停頓時間。
- 按線程數(shù)
- 按照工作模式
可以分為并發(fā)式垃圾回收器和獨占式垃圾回收器昌执。并發(fā)式垃圾回收器與應用程序線程交替工作烛亦,以盡可能減少應用程序的停頓時間;獨占式垃圾回收器 (Stop the world) 一旦運行懂拾,就停止應用程序中的其他所有線程煤禽,直到垃圾回收過程完全結束。
- 按照工作模式
- 按碎片處理方式
可分為壓縮式垃圾回收器和非壓縮式垃圾回收器岖赋。壓縮式垃圾回收器會在回收完成后檬果,對存活對象進行壓縮整理,消除回收后的碎片唐断;非壓縮式的垃圾回收器不進行這步操作选脊。
- 按碎片處理方式
- 按工作的內存區(qū)間
又可分為新生代垃圾回收器和老年代垃圾回收器。
- 按工作的內存區(qū)間
垃圾收集器性能指標
可以用以下指標評價一個垃圾處理器的好壞:
吞吐量
指在應用程序的生命周期內脸甘,應用程序所花費的時間和系統(tǒng)總運行時間的比值恳啥。系統(tǒng)總運行時間=應用程序耗時+GC 耗時。如果系統(tǒng)運行了 100min丹诀,GC 耗時 1min钝的,那么系統(tǒng)的吞吐量就是 (100-1)/100=99%。垃圾回收器負載
和吞吐量相反铆遭,垃圾回收器負載指垃圾回收器耗時與系統(tǒng)運行總時間的比值硝桩。停頓時間
指垃圾回收器正在運行時,應用程序的暫停時間枚荣。對于獨占回收器而言亿柑,停頓時間可能會比較長。使用并發(fā)的回收器時棍弄,由于垃圾回收器和應用程序交替運行,程序的停頓時間會變短疟游,但是呼畸,由于其效率很可能不如獨占垃圾回收器,故系統(tǒng)的吞吐量可能會較低颁虐。垃圾回收頻率
指垃圾回收器多長時間會運行一次蛮原。一般來說,對于固定的應用而言另绩,垃圾回收器的頻率應該是越低越好儒陨。通常增大堆空間可以有效降低垃圾回收發(fā)生的頻率花嘶,但是可能會增加回收產(chǎn)生的停頓時間。反應時間
指當一個對象被稱為垃圾后多長時間內蹦漠,它所占據(jù)的內存空間會被釋放椭员。堆分配
不同的垃圾回收器對堆內存的分配方式可能是不同的。一個良好的垃圾收集器應該有一個合理的堆內存區(qū)間劃分笛园。
JVM 垃圾回收器分類
新生代串行收集器
串行收集器主要有兩個特點:第一隘击,它僅僅使用單線程進行垃圾回收;第二研铆,它獨占式的垃圾回收埋同。
在串行收集器進行垃圾回收時,Java 應用程序中的線程都需要暫停棵红,等待垃圾回收的完成凶赁,這樣給用戶體驗造成較差效果。雖然如此逆甜,串行收集器卻是一個成熟虱肄、經(jīng)過長時間生產(chǎn)環(huán)境考驗的極為高效的收集器。
新生代串行處理器使用復制算法忆绰,實現(xiàn)相對簡單浩峡,邏輯處理特別高效,且沒有線程切換的開銷错敢。在諸如單 CPU 處理器或者較小的應用內存等硬件平臺不是特別優(yōu)越的場合翰灾,它的性能表現(xiàn)可以超過并行回收器和并發(fā)回收器。
在 HotSpot 虛擬機中稚茅,使用-XX:+UseSerialGC
參數(shù)可以指定使用新生代串行收集器和老年代串行收集器纸淮。當 JVM 在 Client 模式下運行時,它是默認的垃圾收集器亚享。一次新生代串行收集器的工作輸出日志類似如清單 1 信息 (使用-XX:+PrintGCDetails 開關) 所示:
清單 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]
它顯示了一次垃圾回收前的新生代的內存占用量和垃圾回收后的新生代內存占用量咽块,以及垃圾回收所消耗的時間。
老年代串行收集器
老年代串行收集器使用的是標記-壓縮算法欺税。和新生代串行收集器一樣侈沪,它也是一個串行的、獨占式的垃圾回收器晚凿。由于老年代垃圾回收通常會使用比新生代垃圾回收更長的時間亭罪,因此,在堆空間較大的應用程序中歼秽,一旦老年代串行收集器啟動应役,應用程序很可能會因此停頓幾秒甚至更長時間。
雖然如此,老年代串行回收器可以和多種新生代回收器配合使用箩祥,同時它也可以作為 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ù)設置,表示新生代使用并行收集器盲泛,老年代使用串行收集器濒持,如清單 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ù)設置寺滚,表示新生代和老年代均使用并行回收收集器柑营。如清單 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ū)的內存占用量村视,以及垃圾回收后老年代和永久區(qū)的內存使用量官套。
并行收集器
并行收集器是工作在新生代的垃圾收集器,它只簡單地將串行回收器多線程化蚁孔。它的回收策略奶赔、算法以及參數(shù)和串行回收器一樣。
并行回收器也是獨占式的回收器杠氢,在收集過程中站刑,應用程序會全部暫停。但由于并行回收器使用多線程進行垃圾回收鼻百,因此绞旅,在并發(fā)能力比較強的 CPU 上,它產(chǎn)生的停頓時間要短于串行回收器温艇,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中因悲,并行回收器的效果不會比串行回收器好,由于多線程的壓力勺爱,它的實際表現(xiàn)很可能比串行回收器差晃琳。
開啟并行回收器可以使用參數(shù)-XX:+UseParNewGC
,該參數(shù)設置新生代使用并行收集器琐鲁,老年代使用串行收集器卫旱。
設置參數(shù)-XX:+UseConcMarkSweepGC
可以要求新生代使用并行收集器,老年代使用 CMS
并行收集器工作時的線程數(shù)量可以使用-XX:ParallelGCThreads
參數(shù)指定围段。一般顾翼,最好與 CPU 數(shù)量相當,避免過多的線程數(shù)影響垃圾收集性能蒜撮。在默認情況下,當 CPU 數(shù)量小于 8 個,ParallelGCThreads 的值等于 CPU 數(shù)量段磨,大于 8 個取逾,ParallelGCThreads 的值等于 3+[5*CPU_Count]/8]。
新生代并行回收 (Parallel Scavenge) 收集器
新生代并行回收收集器也是使用復制算法的收集器苹支。從表面上看砾隅,它和并行收集器一樣都是多線程、獨占式的收集器债蜜。但是晴埂,并行回收收集器有一個重要的特點:它非常關注系統(tǒng)的吞吐量。
新生代并行回收收集器可以使用以下參數(shù)啟用:
-XX:+UseParallelGC
: 新生代使用并行回收收集器寻定,老年代使用串行收集器儒洛。
-XX:+UseParallelOldGC
: 新生代和老年代都是用并行回收收集器。
清單 11. 設置為 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
: 設置最大垃圾收集停頓時間狼速,它的值是一個大于 0 的整數(shù)琅锻。收集器在工作時會調整 Java 堆大小或者其他一些參數(shù),盡可能地把停頓時間控制在 MaxGCPauseMills 以內向胡。如果希望減少停頓時間恼蓬,而把這個值設置得很小,為了達到預期的停頓時間僵芹,JVM 可能會使用一個較小的堆 (一個小堆比一個大堆回收快)处硬,而這將導致垃圾回收變得很頻繁,從而增加了垃圾回收總時間拇派,降低了吞吐量荷辕。
-XX:+GCTimeRatio
:設置吞吐量大小,它的值是一個 0-100 之間的整數(shù)攀痊。假設 GCTimeRatio 的值為 n桐腌,那么系統(tǒng)將花費不超過 1/(1+n) 的時間用于垃圾收集。比如 GCTimeRatio 等于 19苟径,則系統(tǒng)用于垃圾收集的時間不超過 1/(1+19)=5%案站。默認情況下,它的取值是 99棘街,即不超過 1%的時間用于垃圾收集蟆盐。
除此之外,并行回收收集器與并行收集器另一個不同之處在于遭殉,它支持一種自適應的 GC 調節(jié)策略石挂,使用-XX:+UseAdaptiveSizePolicy 可以打開自適應 GC 策略。在這種模式下险污,新生代的大小痹愚、eden 和 survivor 的比例富岳、晉升老年代的對象年齡等參數(shù)會被自動調整,以達到在堆大小拯腮、吞吐量和停頓時間之間的平衡點窖式。在手工調優(yōu)比較困難的場合,可以直接使用這種自適應的方式动壤,僅指定虛擬機的最大堆萝喘、目標的吞吐量 (GCTimeRatio) 和停頓時間 (MaxGCPauseMills),讓虛擬機自己完成調優(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)
它也顯示了收集器的工作成果阁簸,也就是回收前的內存大小和回收后的內存大小,以及花費的時間哼丈。
老年代并行回收收集器
老年代的并行回收收集器也是一種多線程并發(fā)的收集器启妹。和新生代并行回收收集器一樣,它也是一種關注吞吐量的收集器削祈。老年代并行回收收集器使用標記-壓縮算法翅溺,JDK1.6 之后開始啟用。
使用-XX:+UseParallelOldGC
可以在新生代和老生代都使用并行回收收集器髓抑,這是一對非常關注吞吐量的垃圾收集器組合咙崎,在對吞吐量敏感的系統(tǒng)中,可以考慮使用吨拍。參數(shù)-XX:ParallelGCThreads
也可以用于設置垃圾回收時的線程數(shù)量褪猛。
清單 13. 老年代并行回收收集器設置 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 收集器
與并行回收收集器不同,CMS 收集器主要關注于系統(tǒng)停頓時間羹饰。CMS 是 Concurrent Mark Sweep 的縮寫伊滋,意為并發(fā)標記清除,從名稱上可以得知队秩,它使用的是標記-清除算法笑旺,同時它又是一個使用多線程并發(fā)回收的垃圾收集器。
CMS 工作時馍资,主要步驟有:初始標記筒主、并發(fā)標記、重新標記鸟蟹、并發(fā)清除和并發(fā)重置乌妙。其中初始標記和重新標記是獨占系統(tǒng)資源的,而并發(fā)標記建钥、并發(fā)清除和并發(fā)重置是可以和用戶線程一起執(zhí)行的藤韵。因此,從整體上來說熊经,CMS 收集不是獨占式的泽艘,它可以在應用程序運行過程中進行垃圾回收欲险。
根據(jù)標記-清除算法,初始標記匹涮、并發(fā)標記和重新標記都是為了標記出需要回收的對象盯荤。并發(fā)清理則是在標記完成后,正式回收垃圾對象焕盟;并發(fā)重置是指在垃圾回收完成后,重新初始化 CMS 數(shù)據(jù)結構和數(shù)據(jù)宏粤,為下一次垃圾回收做好準備脚翘。并發(fā)標記、并發(fā)清理和并發(fā)重置都是可以和應用程序線程一起執(zhí)行的绍哎。
CMS 收集器在其主要的工作階段雖然沒有暴力地徹底暫停應用程序線程来农,但是由于它和應用程序線程并發(fā)執(zhí)行,相互搶占 CPU崇堰,所以在 CMS 執(zhí)行期內對應用程序吞吐量造成一定影響沃于。CMS 默認啟動的線程數(shù)是 (ParallelGCThreads+3)/4),ParallelGCThreads 是新生代并行收集器的線程數(shù),也可以通過-XX:ParallelCMSThreads 參數(shù)手工設定 CMS 的線程數(shù)量海诲。當 CPU 資源比較緊張時繁莹,受到 CMS 收集器線程的影響,應用程序的性能在垃圾回收階段可能會非常糟糕特幔。
由于 CMS 收集器不是獨占式的回收器咨演,在 CMS 回收過程中,應用程序仍然在不停地工作蚯斯。在應用程序工作過程中薄风,又會不斷地產(chǎn)生垃圾。這些新生成的垃圾在當前 CMS 回收過程中是無法清除的拍嵌。同時遭赂,因為應用程序沒有中斷,所以在 CMS 回收過程中横辆,還應該確保應用程序有足夠的內存可用撇他。因此,CMS 收集器不會等待堆內存飽和時才進行垃圾回收龄糊,而是當前堆內存使用率達到某一閾值時逆粹,便開始進行回收,以確保應用程序在 CMS 工作過程中依然有足夠的空間支持應用程序運行炫惩。
這個回收閾值可以使用-XX:CMSInitiatingOccupancyFraction 來指定僻弹,默認是 68。即當老年代的空間使用率達到 68%時他嚷,會執(zhí)行一次 CMS 回收蹋绽。如果應用程序的內存使用率增長很快芭毙,在 CMS 的執(zhí)行過程中,已經(jīng)出現(xiàn)了內存不足的情況卸耘,此時退敦,CMS 回收將會失敗,JVM 將啟動老年代串行收集器進行垃圾回收蚣抗。如果這樣侈百,應用程序將完全中斷,直到垃圾收集完成翰铡,這時钝域,應用程序的停頓時間可能很長。因此锭魔,根據(jù)應用程序的特點例证,可以對-XX:CMSInitiatingOccupancyFraction 進行調優(yōu)。如果內存增長緩慢迷捧,則可以設置一個稍大的值织咧,大的閾值可以有效降低 CMS 的觸發(fā)頻率,減少老年代回收的次數(shù)可以較為明顯地改善應用程序性能漠秋。反之笙蒙,如果應用程序內存使用率增長很快,則應該降低這個閾值庆锦,以避免頻繁觸發(fā)老年代串行收集器手趣。
標記-清除算法將會造成大量內存碎片,離散的可用空間無法分配較大的對象肥荔。在這種情況下绿渣,即使堆內存仍然有較大的剩余空間,也可能會被迫進行一次垃圾回收燕耿,以換取一塊可用的連續(xù)內存中符,這種現(xiàn)象對系統(tǒng)性能是相當不利的,為了解決這個問題誉帅,CMS 收集器還提供了幾個用于內存壓縮整理的算法淀散。
-XX:+UseCMSCompactAtFullCollection
參數(shù)可以使 CMS 在垃圾收集完成后,進行一次內存碎片整理蚜锨。內存碎片的整理并不是并發(fā)進行的档插。-XX:CMSFullGCsBeforeCompaction 參數(shù)可以用于設定進行多少次 CMS 回收后,進行一次內存壓縮亚再。
-XX:CMSInitiatingOccupancyFraction
設置為 100郭膛,同時設置-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 收集器的目標是作為一款服務器的垃圾收集器氛悬,因此则剃,它在吞吐量和停頓控制上耘柱,預期要優(yōu)于 CMS 收集器。
與 CMS 收集器相比棍现,G1 收集器是基于標記-壓縮算法的调煎。因此,它不會產(chǎn)生空間碎片己肮,也沒有必要在收集完成后士袄,進行一次獨占式的碎片整理工作。G1 收集器還可以進行非常精確的停頓控制谎僻。它可以讓開發(fā)人員指定當停頓時長為 M 時窖剑,垃圾回收時間不超過 N。使用參數(shù)-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC
來啟用 G1 回收器戈稿,設置 G1 回收器的目標停頓時間:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200
。
收集器對系統(tǒng)性能的影響
通過清單 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();//保護內存不溢出
System.out.println("clean map");
}
byte[] b1;
for(int j=0;j<100;j++){
b1 = new byte[512];
map.put(System.nanoTime(), b1);//不斷消耗內存
}
}
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ù)堪伍,對于代碼段的整體執(zhí)行時間有較大的影響锚烦。需要讀者有針對性地選用適合自己代碼段的垃圾回收機制。
GC 相關參數(shù)總結
1. 與串行回收器相關的參數(shù)
-XX:+UseSerialGC
: 在新生代和老年代使用串行回收器帝雇。
-XX:+SurvivorRatio
: 設置 eden 區(qū)大小和 survivor 區(qū)大小的比例涮俄。
-XX:+PretenureSizeThreshold
: 設置大對象直接進入老年代的閾值。當對象的大小超過這個值時尸闸,將直接在老年代分配彻亲。
-XX:MaxTenuringThreshold
:`設置對象進入老年代的年齡的最大值。每一次 Minor GC 后吮廉,對象年齡就加 1苞尝。任何大于這個年齡的對象,一定會進入老年代宦芦。
2. 與并行 GC 相關的參數(shù)
-XX:+UseParNewGC
: `在新生代使用并行收集器宙址。
-XX:+UseParallelOldGC
: 老年代使用并行回收收集器。
-XX:ParallelGCThreads
:設置用于垃圾回收的線程數(shù)调卑。通常情況下可以和 CPU 數(shù)量相等曼氛。但在 CPU 數(shù)量比較多的情況下豁辉,設置相對較小的數(shù)值也是合理的。
-XX:MaxGCPauseMills
:設置最大垃圾收集停頓時間舀患。它的值是一個大于 0 的整數(shù)徽级。收集器在工作時,會調整 Java 堆大小或者其他一些參數(shù)聊浅,盡可能地把停頓時間控制在 MaxGCPauseMills 以內餐抢。
-XX:GCTimeRatio
: 設置吞吐量大小,它的值是一個 0-100 之間的整數(shù)低匙。假設 GCTimeRatio 的值為 n旷痕,那么系統(tǒng)將花費不超過 1/(1+n) 的時間用于垃圾收集。
-XX:+UseAdaptiveSizePolicy
: 打開自適應 GC 策略顽冶。在這種模式下欺抗,新生代的大小,eden 和 survivor 的比例强重、晉升老年代的對象年齡等參數(shù)會被自動調整绞呈,以達到在堆大小、吞吐量和停頓時間之間的平衡點间景。
3. 與 CMS 回收器相關的參數(shù)
-XX:+UseConcMarkSweepGC
: 新生代使用并行收集器佃声,老年代使用 CMS+串行收集器。
-XX:+ParallelCMSThreads
: 設定 CMS 的線程數(shù)量倘要。
-XX:+CMSInitiatingOccupancyFraction
: 設置 CMS 收集器在老年代空間被使用多少后觸發(fā)圾亏,默認為 68%。
-XX:+UseFullGCsBeforeCompaction
: 設定進行多少次 CMS 垃圾回收后封拧,進行一次內存壓縮志鹃。
-XX:+CMSClassUnloadingEnabled
: 允許對類元數(shù)據(jù)進行回收。
-XX:+CMSParallelRemarkEndable
: 啟用并行重標記泽西。
-XX:CMSInitatingPermOccupancyFraction
: 當永久區(qū)占用率達到這一百分比后弄跌,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
-XX:UseCMSInitatingOccupancyOnly
: 表示只在到達閾值的時候尝苇,才進行 CMS 回收铛只。
-XX:+CMSIncrementalMode
: 使用增量模式,比較適合單 CPU糠溜。
4. 與 G1 回收器相關的參數(shù)
-XX:+UseG1GC
:使用 G1 回收器淳玩。
-XX:+UnlockExperimentalVMOptions
:允許使用實驗性參數(shù)。
-XX:+MaxGCPauseMills
: 設置最大垃圾收集停頓時間非竿。
-XX:+GCPauseIntervalMills
:設置停頓間隔時間蜕着。
5. 其他參數(shù)
-XX:+DisableExplicitGC
: 禁用顯示 GC。
結束語
通過本文的學習,讀者可以掌握基本的 JVM 垃圾回收器設計原理及使用規(guī)范承匣”统耍基于筆者多年的工作經(jīng)驗,沒有哪一條優(yōu)化是可以照本宣科的韧骗,它一定是基于您對 JVM 垃圾回收器工作原理及自身程序設計有一定了解前提下嘉抒,通過大量的實驗來找出最適合自己的優(yōu)化方案。