前言
這段時間懈怠了泳挥,罪過!
最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊真仲,這博客我也不推廣袋马,默默的靜靜的,主要是擔(dān)心自己堅持不了秸应。以前寫過時間事件日志現(xiàn)在也不寫了虑凛;寫過博客也不寫了;月記也不寫了桑谍。
堅持平凡事就是偉大胧辽,本來計劃一周一篇的,這次沒有嚴(yán)格執(zhí)行鸭廷。懈怠了
這個GC跟JVM內(nèi)容太多了,理論性東西多些嗜暴,少年時還能記個八九成,好久沒弄展蒂,都忘記了。這次權(quán)當(dāng)整理溫習(xí),再看看《深入理解JVM虛擬機》臼婆,找些過去寫的博客挖點東西過來!
GC
Java GC(Garbage Collection,垃圾收集畔勤,垃圾回收)機制内颗,是Java與C++/C的主要區(qū)別之一,作為Java開發(fā)者,一般不需要專門編寫內(nèi)存回收和垃圾清理代碼得糜,對內(nèi)存泄露和溢出的問題华畏,也不需要像C程序員那樣戰(zhàn)戰(zhàn)兢兢。這是因為在Java虛擬機中皮获,存在自動內(nèi)存管理和垃圾清掃機制丑勤。概括地說,該機制對虛擬機中的內(nèi)存進行標(biāo)記藻雪,并確定哪些內(nèi)存需要回收,根據(jù)一定的回收策略鸠信,自動的回收內(nèi)存,永不停息(Nerver Stop)的保證虛擬機中的內(nèi)存空間词爬,防止出現(xiàn)內(nèi)存泄露和溢出問題。
主要從這幾個問題入手,就差不多了
- Java內(nèi)存區(qū)域
- 哪些內(nèi)存需要回收?
- 什么時候回收
- 如何回收
- 監(jiān)控和優(yōu)化GC
Java內(nèi)存區(qū)域
- 程序計數(shù)器(Program Counter Register)
程序計數(shù)器是一個比較小的內(nèi)存區(qū)域踱卵,用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行聋涨,可以理解為是當(dāng)前線程的行號指示器晨汹。字節(jié)碼解釋器在工作時,會通過改變這個計數(shù)器的值來取下一條語句指令弱卡。每個程序計數(shù)器只用來記錄一個線程的行號堂油,所以它是線程私有(一個線程就有一個程序計數(shù)器)的系宜。
如果程序執(zhí)行的是一個Java方法,則計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令地址轨帜;如果正在執(zhí)行的是一個本地(native,由C語言編寫完成)方法衩椒,則計數(shù)器的值為Undefined蚌父,由于程序計數(shù)器只是記錄當(dāng)前指令地址哮兰,所以不存在內(nèi)存溢出的情況,因此苟弛,程序計數(shù)器也是所有JVM內(nèi)存區(qū) 域中唯一一個沒有定義OutOfMemoryError的區(qū)域喝滞。
- 虛擬機棧(JVM Stack)
一個線程的每個方法在執(zhí)行的同時,都會創(chuàng)建一個棧幀(Statck Frame)膏秫,棧幀中存儲的有局部變量表右遭、操作站、動態(tài)鏈接缤削、方法出口等窘哈,當(dāng)方法被調(diào)用時,棧幀在JVM棧中入棧亭敢,當(dāng)方法執(zhí)行完成時滚婉,棧幀出棧。局部變量表中存儲著方法的相關(guān)局部變量吨拗,包括各種基本數(shù)據(jù)類型满哪,對象的引用,返回地址等劝篷。在局部變量表中哨鸭,只有l(wèi)ong和double類型會占用2個局部變量空間(Slot,對于32位機器娇妓,一個Slot就是32個bit)像鸡,其它都是1個Slot。需要注意的是哈恰,局部變量表是在編譯時就已經(jīng)確定 好的只估,方法運行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內(nèi)都不會改變着绷。虛擬機棧中定義了兩種異常蛔钙,如果線程調(diào)用的棧深度大于虛擬機允許的最大深度,則拋出StatckOverFlowError(棧溢出)荠医;不過多 數(shù)Java虛擬機都允許動態(tài)擴展虛擬機棧的大小(有少部分是固定長度的)吁脱,所以線程可以一直申請棧,知道內(nèi)存不足彬向,此時兼贡,會拋出 OutOfMemoryError(內(nèi)存溢出)。每個線程對應(yīng)著一個虛擬機棧娃胆,因此虛擬機棧也是線程私有的遍希。
- 本地方法棧(Native Method Statck):
本地方法棧在作用,運行機制里烦,異常類型等方面都與虛擬機棧相同凿蒜,唯一的區(qū)別是:虛擬機棧是執(zhí)行Java方法的禁谦,而本地方法棧是用來執(zhí)行native方法的,在很多虛擬機中(如Sun的JDK默認(rèn)的HotSpot虛擬機)废封,會將本地方法棧與虛擬機棧放在一起使用枷畏。本地方法棧也是線程私有的。
- 堆區(qū)(Heap)
堆區(qū)是理解Java GC機制最重要的區(qū)域虱饿,沒有之一。在JVM所管理的內(nèi)存中触趴,堆區(qū)是最大的一塊氮发,堆區(qū)也是Java GC機制所管理的主要內(nèi)存區(qū)域,堆區(qū)由所有線程共享冗懦,在虛擬機啟動時創(chuàng)建爽冕。堆區(qū)的存在是為了存儲對象實例,原則上講披蕉,所有的對象都在堆區(qū)上分配內(nèi)存(不過現(xiàn)代技術(shù)里颈畸,也不是這么絕對的,也有棧上直接分配的)没讲。一般的眯娱,根據(jù)Java虛擬機規(guī)范規(guī)定,堆內(nèi)存需要在邏輯上是連續(xù)的(在物理上不需要)爬凑,在實現(xiàn)時徙缴,可以是固定大小的,也可以是可擴展的嘁信,目前主流的虛擬機都是可擴展的于样。如果在執(zhí)行垃圾回收之后,仍沒有足夠的內(nèi)存分配潘靖,也不能再擴展穿剖,將會拋出OutOfMemoryError:Java heap space異常。
-Xms 參數(shù)設(shè)置最小值
-Xmx 參數(shù)設(shè)置最大值
例:VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
若-Xms=-Xmx,則可避免堆自動擴展卦溢。
-XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機在出現(xiàn)內(nèi)存溢出是dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲快照糊余。
- 方法區(qū)(Method Area)
在Java虛擬機規(guī)范中,將方法區(qū)作為堆的一個邏輯部分來對待既绕,但事實上啄刹,方法區(qū)并不是堆(Non-Heap);另外凄贩,不少人的博客中誓军,將Java GC的分代收集機制分為3個代:青年代,老年代疲扎,永久代昵时,這些作者將方法區(qū)定義為“永久代”捷雕,這是因為,對于之前的HotSpot Java虛擬機的實現(xiàn)方式中壹甥,將分代收集的思想擴展到了方法區(qū)救巷,并將方法區(qū)設(shè)計成了永久代。不過句柠,除HotSpot之外的多數(shù)虛擬機浦译,并不將方法區(qū)當(dāng)做永久代,HotSpot本身溯职,也計劃取消永久代精盅。
方法區(qū)是各個線程共享的區(qū)域,用于存儲已經(jīng)被虛擬機加載的類信息(即加載類時需要加載的信息谜酒,包括版本叹俏、field、方法僻族、接口等信息)粘驰、final常量、靜態(tài)變量述么、編譯器即時編譯的代碼等蝌数。方法區(qū)在物理上也不需要是連續(xù)的,可以選擇固定大小或可擴展大小碉输,并且方法區(qū)比堆還多了一個限制:可以選擇是否執(zhí)行垃圾收集籽前。一般的,方法區(qū)上 執(zhí)行的垃圾收集是很少的敷钾,這也是方法區(qū)被稱為永久代的原因之一(HotSpot)枝哄,但這也不代表著在方法區(qū)上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內(nèi)存回收和對已加載類的卸載阻荒。在方法區(qū)上進行垃圾收集挠锥,條件苛刻而且相當(dāng)困難,效果也不令人滿意侨赡,所以一般不做太多考慮蓖租,可以留作以后進一步深入研究時使用。在方法區(qū)上定義了OutOfMemoryError:PermGen space異常羊壹,
在內(nèi)存不足時拋出蓖宦。
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分,用于存儲編譯期就生成的字面常量油猫、符號引用稠茂、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個變量、接口的位置,直接引用就是根據(jù)符號引用翻譯出來的地址睬关,將在類鏈接階段完成翻譯)诱担;運行時常量池除了存儲編譯期常量外,也可以存儲在運行時間產(chǎn)生的常量(比如String類的intern()方法电爹,作用是String維護了一個常量池蔫仙,如果調(diào)用的字符“abc”已經(jīng)在常量池中,則返回池中的字符串地址丐箩,否則摇邦,新建一個常量加入池中,并返回地址)屎勘。
-XX:MaxPermSize 設(shè)置上限
-XX:PermSize 設(shè)置最小值
例:VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
- 直接內(nèi)存(Direct Memory)
直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分涎嚼,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用挑秉,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn),所以我們放到這里一起講解苔货。
Direct Memory滿了之后犀概,系統(tǒng)不會自動回收這段內(nèi)存; 而是要等Tenured Generation滿觸發(fā)GC時夜惭,Direct Memory才會被跟著回收姻灶。
在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式诈茧,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存产喉,然后通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作。這樣能在一些場景中顯著提高性能敢会,因為避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)曾沈。
顯然,本機直接內(nèi)存的分配不會受到Java堆大小的限制鸥昏,但是塞俱,既然是內(nèi)存,則肯定還是會受到本機總內(nèi)存(包括RAM及SWAP區(qū)或者分頁文件)的大小及處理器尋址空間的限制吏垮。服務(wù)器管理員配置虛擬機參數(shù)時障涯,一般會根據(jù)實際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常會忽略掉直接內(nèi)存膳汪,使得各個內(nèi)存區(qū)域的總和大于物理內(nèi)存限制(包括物理上的和操作系統(tǒng)級的限制)唯蝶,從而導(dǎo)致動態(tài)擴展時出現(xiàn)OutOfMemoryError異常。
-XX:MaxDirectMemorySize 設(shè)置最大值遗嗽,默認(rèn)與java堆最大值一樣粘我。
例 :-XX:MaxDirectMemorySize=10M -Xmx20M
哪些內(nèi)存被回收
根據(jù)運行時數(shù)據(jù)區(qū)域的各個部分,程序計數(shù)器媳谁、虛擬機棧涂滴、本地方法棧三個區(qū)域隨著線程而生友酱,隨線程滅而滅。棧中的棧幀隨著方法的進入和退出而進棧出棧柔纵。每個棧幀分配多少內(nèi)存在類結(jié)構(gòu)確定下來的時候就基本已經(jīng)確定缔杉。所以這個三個區(qū)域內(nèi)存回收時方法或者線程結(jié)束而回收的,不需要太多關(guān)注搁料;而java堆和方法區(qū)則不一樣或详,一個接口不同實現(xiàn)類,一個方法中不同的分支郭计,在具體運行的時候才能確定創(chuàng)建那些對象霸琴,所以這部分內(nèi)存是動態(tài)的,也是需要垃圾回收機制來回收處理的昭伸。
- 堆內(nèi)存
判斷堆內(nèi)的對象是否可以回收梧乘,要判斷這個對象實例是否確實沒用,判斷算法有兩種:引用計數(shù)法和根搜索算法庐杨。
引用計數(shù)法:就是給每個對象加一個計數(shù)器选调,如果有一個地方引用就加1,當(dāng)引用失效就減1;當(dāng)計數(shù)器為0,則認(rèn)為對象是無用的球匕。這種算法最大的問題在于不能解決相互引用的對象,如:A.b=B;B.a=A弦聂,在沒有其他引用的情況下,應(yīng)該回收氛什;但按照引用計數(shù)法來計算莺葫,他們的引用都不為0,顯然不能回收枪眉。
根搜索算法:這個算法的思路是通過一系列名為“GC Roots”的對象作為起點徙融,
從這個節(jié)點向下搜索,搜索所經(jīng)過的路徑稱為引用鏈(Reference Chain)瑰谜,當(dāng)一個對象到GC Roots沒有任何引用鏈相連(圖論的不可達)時欺冀,則證明該對象不可用。
java等一大部分商用語言是用根搜索算法來管理內(nèi)存的萨脑,java中可以做為GC Roots的對象有如下幾種:
虛擬機棧(棧幀中的本地變量表)中的引用的對象隐轩;
方法區(qū)中的類靜態(tài)屬性引用的對象;
方法區(qū)中常量引用的對象渤早;
本地方法棧JNI(Native)的引用對象职车;
無論是通過引用計數(shù)算法判斷對象的引用數(shù)量,還是通過可達性分析算法判斷對象的引用鏈?zhǔn)欠窨蛇_,判定對象是否存活都與“引用”有關(guān)悴灵。在JDK1.2以前扛芽,Java中的引用的定義很傳統(tǒng)如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個引用积瞒。這種定義很純粹川尖,但是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài)茫孔,對于如何描述一些“食之無味叮喳,棄之可惜”的對象就顯得無能為力。我們希望能描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時缰贝,則能保留在內(nèi)存之中馍悟;如果內(nèi)存空間在進行垃圾收集后還是非常緊張,則可以拋棄這些對象剩晴。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景锣咒。
在JDK 1.2之后,Java對引用的概念進行了擴充赞弥,將引用分為強引用(Strong Reference)宠哄、軟引用(Soft Reference)、弱引用(Weak Reference)嗤攻、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱诽俯。
- 強引用
只要強引用還存在妇菱,垃圾收集器永遠不會收掉被引用的對象
- 軟引用
在系統(tǒng)將要發(fā)生內(nèi)存異常之前,將會把這些對象列進回收范圍之中進行第二次回收暴区。
- 弱引用
被弱引用關(guān)聯(lián)的對象只能生存道下一次垃圾收集發(fā)生之前闯团。
- 虛引用
一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響仙粱,也無法通過虛引用來取得一個對象的實例房交。
finalize()方法
在Object類中
protected void finalize() throws Throwable { }
注意下這個訪問控制符是protected
finalize()在什么時候被調(diào)用?
有三種情況
- 所有對象被Garbage Collection時自動調(diào)用,比如運行System.gc()的時候.
- 程序退出時為每個對象調(diào)用一次finalize方法。
- 顯式的調(diào)用finalize方法
當(dāng)一個對象不可到達時伐割,并不是馬上就被回收的候味。
當(dāng)對象沒有覆蓋finalize()方法,或者finalized()已經(jīng)被JVM調(diào)用過隔心,那就是沒有必要執(zhí)行finalzied()
;Finalizer線程執(zhí)行它白群,但并不保證等待它執(zhí)行結(jié)束,這主要是防止finalize()出現(xiàn)問題硬霍,導(dǎo)致Finalizer線程無限等待帜慢,整個內(nèi)存回收系統(tǒng)崩潰
具體的finalize流程:
對象可由兩種狀態(tài),涉及到兩類狀態(tài)空間,一是終結(jié)狀態(tài)空間 F = {unfinalized, finalizable, finalized}粱玲;二是可達狀態(tài)空間 R = {reachable, finalizer-reachable, unreachable}躬柬。各狀態(tài)含義如下:
unfinalized: 新建對象會先進入此狀態(tài),GC并未準(zhǔn)備執(zhí)行其finalize方法抽减,因為該對象是可達的
finalizable: 表示GC可對該對象執(zhí)行finalize方法允青,GC已檢測到該對象不可達。正如前面所述胯甩,GC通過F-Queue隊列和一專用線程完成finalize的執(zhí)行
finalized: 表示GC已經(jīng)對該對象執(zhí)行過finalize方法
reachable: 表示GC Roots引用可達
finalizer-reachable(f-reachable):表示不是reachable昧廷,但可通過某個finalizable對象可達
unreachable:對象不可通過上面兩種途徑可達
- 新建對象首先處于[reachable, unfinalized]狀態(tài)(A)
- 隨著程序的運行,一些引用關(guān)系會消失偎箫,導(dǎo)致狀態(tài)變遷木柬,從reachable狀態(tài)變遷到f-reachable(B, C, D)或unreachable(E, F)狀態(tài)
- 若JVM檢測到處于unfinalized狀態(tài)的對象變成f-reachable或unreachable,JVM會將其標(biāo)記為finalizable狀態(tài)(G,H)淹办。若對象原處于[unreachable, unfinalized]狀態(tài)眉枕,則同時將其標(biāo)記為f-reachable(H)。
- 在某個時刻怜森,JVM取出某個finalizable對象速挑,將其標(biāo)記為finalized并在某個線程中執(zhí)行其finalize方法。由于是在活動線程中引用了該對象副硅,該對象將變遷到(reachable, finalized)狀態(tài)(K或J)姥宝。該動作將影響某些其他對象從f-reachable狀態(tài)重新回到reachable狀態(tài)(L, M, N), 這就是對象重生
- 處于finalizable狀態(tài)的對象不能同時是unreahable的,由第4點可知恐疲,將對象finalizable對象標(biāo)記為finalized時會由某個線程執(zhí)行該對象的finalize方法腊满,致使其變成reachable。這也是圖中只有八個狀態(tài)點的原因
- 程序員手動調(diào)用finalize方法并不會影響到上述內(nèi)部標(biāo)記的變化培己,因此JVM只會至多調(diào)用finalize一次碳蛋,即使該對象“復(fù)活”也是如此。程序員手動調(diào)用多少次不影響JVM的行為
- 若JVM檢測到finalized狀態(tài)的對象變成unreachable省咨,回收其內(nèi)存(I)
- 若對象并未覆蓋finalize方法肃弟,JVM會進行優(yōu)化,直接回收對象(O)
注:System.runFinalizersOnExit()等方法可以使對象即使處于reachable狀態(tài)零蓉,JVM仍對其執(zhí)行finalize方法
對finalize()的一句話概括:
JVM能夠保證一個對象在回收以前一定會調(diào)用一次它的finalize()方法笤受。這句話中兩個陷阱:回收以前一定和一次
但有很多地方是講,JVM不承諾這一定調(diào)用finalize()敌蜂,這就是上面的陷阱造成的
你永遠不知道它什么時候被調(diào)用甚至?xí)粫{(diào)用(因為有些對象是永遠不會被回收的感论,或者被回收以前程序就結(jié)束了),但如果他是有必要執(zhí)行finalize()的,那在GC前一定調(diào)用一次且僅一次紊册,如果在第一次GC時沒有被回收比肄,那以后再GC時快耿,就不再調(diào)用finalize()
- 方法區(qū)
很多人認(rèn)為方法區(qū)(或者HotSpot虛擬機中的永久代)是沒有垃圾收集的,Java虛擬機規(guī)范中確實說過可以不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集芳绩,而且在方法區(qū)進行垃圾收集的“性價比”一般比較低:在堆中掀亥,尤其是在新生代中,常規(guī)應(yīng)用進行一次垃圾收集*++一般可以回收70%~95%的空間++妥色,而永久代的垃圾收集效率遠低于此搪花。
方法區(qū)回收主要有兩部分:廢棄的常量和無用的類。廢棄的常量判斷方法和堆中的對象類似嘹害,只要判斷沒有地方引用就可以回收撮竿。相比之下,判斷一個類是否無用笔呀,條件就比較苛刻幢踏,需要同時滿足下面3個條件才能算是“無用的類”:
- 該類的所有實例都已經(jīng)被回收,也就是java堆中不存在該類的任何實例许师;
- 加載該類的ClassLoader已經(jīng)被回收房蝉;
- 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法微渠。
虛擬機可以對滿足上述3個條件的無用類進行回收搭幻,這里說的僅僅是“可以”,而不是和對象一樣逞盆,不使用了就必然會回收檀蹋。是否對類進行回收,
HotSpot虛擬機提供了
-Xnoclassgc參數(shù)進行控制云芦,
還可以使用
-verbose:class
-XX:+TraceClassLoading
-XX:+TraceClassUnLoading查看類的加載和卸載信息俯逾。
在大量使用反射、動態(tài)代理焕数、CGLib等bytecode框架的場景,以及動態(tài)生成JSP和OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能刨啸,以保證永久代不會溢出
如何回收
選擇合適的GC collector是JVM調(diào)優(yōu)最重要的一項堡赔,前提是先了解回收算法
“標(biāo)記-清除”(Mark-Sweep)
算法分為“標(biāo)記”和“清除”兩個階段:
首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對象
主要缺點有兩個
- 一個是效率問題设联,標(biāo)記和清除過程的效率都不高
- 一個是空間問題善已,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致离例,當(dāng)程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作
“復(fù)制”(Copying)
它將可用內(nèi)存按容量劃分為大小相等的兩塊换团,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了宫蛆,就將還存活著的對象復(fù)制到另外一塊上面艘包,然后再把已使用過的內(nèi)存空間一次清理掉的猛。這樣使得每次都是對其中的一塊進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況想虎,只要移動堆頂指針卦尊,按順序分配內(nèi)存即可,實現(xiàn)簡單舌厨,運行高效岂却。
只是這種算法的代價是將內(nèi)存縮小為原來的一半,未免太高了一點
現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代裙椭,IBM的專門研究表明躏哩,新生代中的對象98%是朝生夕死的,所以并不需要按照1∶1的比例來劃分內(nèi)存空間揉燃,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間扫尺,每次使用Eden和其中的一塊Survivor。當(dāng)回收時你雌,將Eden和Survivor中還存活著的對象一次性地拷貝到另外一塊Survivor空間上器联,最后清理掉Eden和剛才用過的Survivor的空間。HotSpot虛擬機默認(rèn)Eden和Survivor的大小比例是8∶1婿崭,也就是每次新生代中可用內(nèi)存空間為整個新生代容量的90%(80%+10%)拨拓,只有10%的內(nèi)存是會被“浪費”的。當(dāng)然氓栈,98%的對象可回收只是一般場景下的數(shù)據(jù)渣磷,我們沒有辦法保證每次回收都只有不多于10%的對象存活,當(dāng)Survivor空間不夠用時授瘦,需要依賴其他內(nèi)存(這里指老年代)進行分配擔(dān)保(Handle Promotion)醋界。
在對象存活率較高時就要執(zhí)行較多的復(fù)制操作,效率將會變低提完。更關(guān)鍵的是形纺,如果不想浪費50%的空間,就需要有額外的空間進行分配擔(dān)保徒欣,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況逐样,所以在老年代一般不能直接選用這種算法。
-XX:SurvivorRatio=4
設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值打肝。
設(shè)置為4脂新,則Eden區(qū)與兩個Survivor區(qū)的比值為4:1:1,一個Survivor區(qū)占整個年輕代的1/6
為什么新生代有兩個survivor?
StackOverflow上面給出的解釋是:
The reason for the HotSpot JVM's two survivor spaces is to reduce the need to deal with fragmentation. New objects are allocated in eden space. All well and good. When that's full, you need a GC, so kill stale objects and move live ones to a survivor space, where they can mature for a while before being promoted to the old generation. Still good so far. The next time we run out of eden space, though, we have a conundrum. The next GC comes along and clears out some space in both eden and our survivor space, but the spaces aren't contiguous. So is it better to
- Try to fit the survivors from eden into the holes in the survivor space that were cleared by the GC?
- Shift all the objects in the survivor space down to eliminate the fragmentation, and then move the survivors into it?
- Just say "screw it, we're moving everything around anyway," and copy all of the survivors from both spaces into a completely separate space--the second survivor space--thus leaving you with a clean eden and survivor space where you can repeat the sequence on the next GC?
Sun's answer to the question is obvious.
“標(biāo)記-整理”(Mark-Compact)
此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個算法的優(yōu)點粗梭。也是分兩階段争便,
- 第一階段從根節(jié)點開始標(biāo)記所有被引用對象,
- 第二階段遍歷整個堆断医,把清除未標(biāo)記對象并且把存活對象“壓縮”到堆的其中一塊滞乙,按順序排放奏纪。此算法避免了“標(biāo)記-清除”的碎片問題,同時也避免了“復(fù)制”算法的空間問題酷宵。
“分代收集”(Generational Collection)
當(dāng)前商業(yè)虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法亥贸,
這種算法并沒有什么新的思想,只是根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊浇垦。
一般是把Java堆分為新生代和老年代炕置,這樣就可以根據(jù)各個年代的特點采用最適當(dāng)?shù)氖占惴āT谛律心腥停看卫占瘯r都發(fā)現(xiàn)有大批對象死去朴摊,只有少量存活此虑,那就選用復(fù)制算法甚纲,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因為對象存活率高朦前、沒有額外空間對它進行分配擔(dān)保介杆,就必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法來進行回收。
新生代 GC(Minor GC):指發(fā)生在新生代的垃圾收集動作韭寸,因為 Java 對象大多都具
備朝生夕滅的特性春哨,所以 Minor GC 非常頻繁,一般回收速度也比較快恩伺。老年代 GC(Major GC):指發(fā)生在老年代的 GC赴背,出現(xiàn)了 Major GC,經(jīng)常
會伴隨至少一次的 Minor GC(但非絕對的晶渠,在 ParallelScavenge 收集器的收集策略里
就有直接進行 Major GC 的策略選擇過程) 凰荚。MajorGC 的速度一般會比 Minor GC 慢 10
倍以上。
虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器褒脯。如果對象在 Eden 出生并經(jīng)過第一次 Minor GC 后仍然存活便瑟,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中番川,并將對象年齡設(shè)為 1到涂。對象在 Survivor 區(qū)中每熬過一次 Minor GC,年齡就增加 1 歲爽彤,當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲)時养盗,就會被晉升到老年代中缚陷。
對象晉升老年代的年齡閾值适篙,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置。
垃圾收集器
按系統(tǒng)線程分
注意并發(fā)(Concurrent)和并行(Parallel)的區(qū)別:
- 并發(fā)是指用戶線程與GC線程同時執(zhí)行(不一定是并行箫爷,可能交替嚷节,但總體上是在同時執(zhí)行的)聂儒,不需要停頓用戶線程(其實在CMS中用戶線程還是需要停頓的,只是非常短硫痰,GC線程在另一個CPU上執(zhí)行)衩婚;
- 并行收集是指多個GC線程并行工作,但此時用戶線程是暫停的效斑;
這個跟傳統(tǒng)的并發(fā)并行概念不同
并行是物理的非春,并發(fā)是邏輯的。
并行是和串行對立缓屠。
Serial收集器
Serial是最基本奇昙、歷史最悠久的垃圾收集器,使用復(fù)制算法敌完,曾經(jīng)是JDK1.3.1之前新生代唯一的垃圾收集器储耐。目前也是ClientVM下 ServerVM 4核4GB以下機器的默認(rèn)垃圾回收器。
串行收集器并不是只能使用一個CPU進行收集滨溉,而是當(dāng)JVM需要進行垃圾回收的時候什湘,需要中斷所有的用戶線程,知道它回收結(jié)束為止晦攒,因此又號稱“Stop The World” 的垃圾回收器闽撤。注意,JVM中文名稱為java虛擬機勤家,因此它就像一臺虛擬的電腦一樣在工作腹尖,而其中的每一個線程就被認(rèn)為是JVM的一個處理器,因此大家看到圖中的CPU0伐脖、CPU1實際為用戶的線程热幔,而不是真正機器的CPU,大家不要誤解哦讼庇。
串行回收方式適合低端機器绎巨,是Client模式下的默認(rèn)收集器,對CPU和內(nèi)存的消耗不高蠕啄,適合用戶交互比較少场勤,后臺任務(wù)較多的系統(tǒng)。
Serial收集器默認(rèn)新舊生代的回收器搭配為Serial+ SerialOld
新生代歼跟、老年代使用串行回收和媳;新生代復(fù)制算法、老年代標(biāo)記-壓縮
在J2SE5.0上哈街,在非server模式下留瞳,JVM自動選擇串行收集器。
也可以顯示進行選擇骚秦,在Java啟動參數(shù)中增加:
-XX:+UseSerialGC
Serial Old收集器
SerialOld是舊生代Client模式下的默認(rèn)收集器她倘,單線程執(zhí)行璧微,使用“標(biāo)記-整理”算法
在Server模式下,主要有兩個用途:
- 在JDK1.5之前版本中與新生代的Parallel Scavenge收集器搭配使用硬梁。
- 作為年老代中使用CMS收集器的后備垃圾收集方案前硫。
ParNew收集器
ParNew收集器其實就是多線程版本的Serial收集器,
Stop The World
他是多CPU模式下的首選回收器(該回收器在單CPU的環(huán)境下回收效率遠遠低于Serial收集器,所以一定要注意場景哦)
Server模式下的默認(rèn)收集器荧止。
新生代并行屹电,老年代串行;新生代復(fù)制算法跃巡、老年代標(biāo)記-壓縮
-XX:+UseParNewGC ParNew收集器
ParNew收集器默認(rèn)開啟和CPU數(shù)目相同的線程數(shù)
-XX:ParallelGCThreads 限制線程數(shù)量
Parallel Scavenge收集器
Parallel Scavenge收集器也是一個新生代垃圾收集器嗤详,同樣使用復(fù)制算法,也是一個多線程的垃圾收集器,也稱吞吐量優(yōu)先的收集器
所提到的吞吐量=程序運行時間/(JVM執(zhí)行回收的時間+程序運行時間),假設(shè)程序運行了100分鐘瓷炮,JVM的垃圾回收占用1分鐘葱色,那么吞吐量就是99%坚芜。在當(dāng)今網(wǎng)絡(luò)告訴發(fā)達的今天伸辟,良好的響應(yīng)速度是提升用戶體驗的一個重要指標(biāo)颠毙,多核并行云計算的發(fā)展要求程序盡可能的使用CPU和內(nèi)存資源描姚,盡快的計算出最終結(jié)果览效,因此在交互不多的云端呢燥,比較適合使用該回收器慎式。
可以通過參數(shù)來打開自適應(yīng)調(diào)節(jié)策略终佛,虛擬機會根據(jù)當(dāng)前系統(tǒng)的運行情況收集性能監(jiān)控信息安接,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或最大的吞吐量翔忽;也可以通過參數(shù)控制GC的時間不大于多少毫秒或者比例
新生代復(fù)制算法、老年代標(biāo)記-壓縮
-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行
Parallel Scavenge收集器提供了兩個參數(shù)用于精準(zhǔn)控制吞吐量:
a.-XX:MaxGCPauseMillis:控制最大垃圾收集停頓時間盏檐,是一個大于0的毫秒數(shù)歇式。
b.-XX:GCTimeRation:直接設(shè)置吞吐量大小,是一個大于0小于100的整數(shù)胡野,
也就是程序運行時間占總時間的比率材失,默認(rèn)值是99,即垃圾收集運行最大1%(1/(1+99))的垃圾收集時間
-XX:+UseAdaptiveSizePolicy硫豆,這是個開關(guān)參數(shù)龙巨,
打開之后就不需要手動指定新生代大小(-Xmn)、Eden與Survivor區(qū)的比例(-XX:SurvivorRation)熊响、
新生代晉升年老代對象年齡(-XX:PretenureSizeThreshold)等細節(jié)參數(shù)
Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本旨别,使用多線程和“標(biāo)記-整理”算法。這個收集器是在JDK 1.6中才開始提供
在JDK1.6之前汗茄,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器秸弛,只能保證新生代的吞吐量優(yōu)先,無法保證整體的吞吐量,Parallel Old正是為了在年老代同樣提供吞吐量優(yōu)先的垃圾收集器胆屿,如果系統(tǒng)對吞吐量要求比較高,可以優(yōu)先考慮新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略
參數(shù)控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
新生代Parallel Scavenge和年老代Parallel Old收集器搭配運行過程圖:
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器偶宫。目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端上非迹,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時間最短纯趋,以給用戶帶來較好的體驗憎兽。
由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程中,收集器線程都可以與用戶線程一起工作吵冒,所以總體上來說纯命,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)地執(zhí)行。老年代收集器(新生代使用ParNew)
優(yōu)點:并發(fā)收集痹栖、低停頓
缺點:產(chǎn)生大量空間碎片亿汞、并發(fā)階段會降低吞吐量
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“標(biāo)記-清除”算法實現(xiàn)的,它的運作過程相對于前面幾種收集器來說要更復(fù)雜一些揪阿,整個過程分為4個步驟疗我,包括:
初始標(biāo)記(CMS initial mark)
并發(fā)標(biāo)記(CMS concurrent mark)
重新標(biāo)記(CMS remark)
并發(fā)清除(CMS concurrent sweep)
a.初始標(biāo)記:只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)的對象,速度很快南捂,仍然需要暫停所有的工作線程吴裤。
b.并發(fā)標(biāo)記:進行GC Roots跟蹤的過程,和用戶線程一起工作溺健,不需要暫停工作線程麦牺。
c.重新標(biāo)記:為了修正在并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄鞭缭,仍然需要暫停所有的工作線程剖膳。
d.并發(fā)清除:清除GC Roots不可達對象,和用戶線程一起工作岭辣,不需要暫停工作線程潮秘。
由于耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程中,垃圾收集線程可以和用戶現(xiàn)在一起并發(fā)工作易结,所以總體上來看CMS收集器的內(nèi)存回收和用戶線程是一起并發(fā)地執(zhí)行枕荞。
CMS收集器工作過程:
其中初始標(biāo)記、重新標(biāo)記這兩個步驟仍然需要“Stop The World”搞动。
CMS收集器有以下三個不足:
CMS收集器對CPU資源非常敏感躏精,其默認(rèn)啟動的收集線程數(shù)=(CPU數(shù)量+3)/4,在用戶程序本來CPU負荷已經(jīng)比較高的情況下鹦肿,如果還要分出CPU資源用來運行垃圾收集器線程矗烛,會使得CPU負載加重。
CMS無法處理浮動垃圾(Floating Garbage),可能會導(dǎo)致Concurrent ModeFailure失敗而導(dǎo)致另一次Full GC瞭吃。由于CMS收集器和用戶線程并發(fā)運行碌嘀,因此在收集過程中不斷有新的垃圾產(chǎn)生,這些垃圾出現(xiàn)在標(biāo)記過程之后歪架,CMS無法在本次收集中處理掉它們股冗,只好等待下一次GC時再將其清理掉,這些垃圾就稱為浮動垃圾和蚪。
CMS垃圾收集器不能像其他垃圾收集器那樣等待年老代機會完全被填滿之后再進行收集止状,需要預(yù)留一部分空間供并發(fā)收集時的使用,可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction來設(shè)置年老代空間達到多少的百分比時觸發(fā)CMS進行垃圾收集攒霹,默認(rèn)是68%怯疤。
如果在CMS運行期間,預(yù)留的內(nèi)存無法滿足程序需要催束,就會出現(xiàn)一次ConcurrentMode Failure失敗集峦,此時虛擬機將啟動預(yù)備方案,使用Serial Old收集器重新進行年老代垃圾回收抠刺。CMS收集器是基于標(biāo)記-清除算法少梁,因此不可避免會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,如果無法找到一塊足夠大的連續(xù)內(nèi)存存放對象時矫付,將會觸發(fā)因此Full GC凯沪。CMS提供一個開關(guān)參數(shù)-XX:+UseCMSCompactAtFullCollection,用于指定在Full GC之后進行內(nèi)存整理买优,內(nèi)存整理會使得垃圾收集停頓時間變長妨马,CMS提供了另外一個參數(shù)-XX:CMSFullGCsBeforeCompaction,用于設(shè)置在執(zhí)行多少次不壓縮的Full GC之后杀赢,跟著再來一次內(nèi)存整理
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后烘跺,進行一次碎片整理;整理過程是獨占的脂崔,會引起停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設(shè)置進行幾次Full GC后滤淳,進行一次碎片整理
-XX:ParallelCMSThreads 設(shè)定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量)
G1收集器
G1可謂博采眾家之長,力求到達一種完美砌左。他吸取了增量收集優(yōu)點脖咐,把整個堆劃分為一個一個等大小的區(qū)域(region)。內(nèi)存的回收和劃分都以region為單位汇歹;同時屁擅,他也吸取了CMS的特點,把這個垃圾回收過程分為幾個階段产弹,分散一個垃圾回收過程派歌;而且,G1也認(rèn)同分代垃圾回收的思想,認(rèn)為不同對象的生命周期不同胶果,可以采取不同收集方式匾嘱,因此,它也支持分代的垃圾回收早抠。為了達到對回收時間的可預(yù)計性霎烙,G1在掃描了region以后,對其中的活躍對象的大小進行排序贝或,首先會收集那些活躍對象小的region,以便快速回收空間(要復(fù)制的活躍對象少了)锐秦,因為活躍對象小咪奖,里面可以認(rèn)為多數(shù)都是垃圾,所以這種方式被稱為Garbage First(G1)的垃圾回收算法酱床,即:垃圾優(yōu)先的回收羊赵。
與CMS收集器相比G1收集器有以下特點:
空間整合,G1收集器采用標(biāo)記整理算法扇谣,不會產(chǎn)生內(nèi)存空間碎片昧捷。分配大對象時不會因為無法找到連續(xù)空間而提前觸發(fā)下一次GC。
可預(yù)測停頓罐寨,這是G1的另一大優(yōu)勢靡挥,降低停頓時間是G1和CMS的共同關(guān)注點,但G1除了追求低停頓外鸯绿,還能建立可預(yù)測的停頓時間模型跋破,能讓使用者明確指定在一個長度為N毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒瓶蝴,這幾乎已經(jīng)是實時Java(RTSJ)的垃圾收集器的特征了毒返。
收集的范圍都是整個新生代或者老年代,而G1不再是這樣舷手。使用G1收集器時拧簸,Java堆的內(nèi)存布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區(qū)域(Region)男窟,雖然還保留有新生代和老年代的概念盆赤,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續(xù))Region的集合歉眷。
G1的新生代收集跟ParNew類似弟劲,當(dāng)新生代占用達到一定比例的時候,開始出發(fā)收集姥芥。
和CMS類似兔乞,G1收集器收集老年代對象會有短暫停頓。
標(biāo)記階段,首先初始標(biāo)記(Initial-Mark),這個階段是停頓的(Stop the World Event)庸追,并且會觸發(fā)一次普通Mintor GC霍骄。對應(yīng)GC log:GC pause (young) (inital-mark)
Root Region Scanning,程序運行過程中會回收survivor區(qū)(存活到老年代)淡溯,這一過程必須在young GC之前完成读整。
-
Concurrent Marking,在整個堆中進行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行)咱娶,此過程可能被young GC中斷米间。在并發(fā)標(biāo)記階段,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑ο蠖际抢煳辏莻€這個區(qū)域會被立即回收(圖中打X)屈糊。同時,并發(fā)標(biāo)記過程中琼了,會計算每個區(qū)域的對象活性(區(qū)域中存活對象的比例)逻锐。
Remark, 再標(biāo)記,會有短暫停頓(STW)雕薪。再標(biāo)記階段是用來收集 并發(fā)標(biāo)記階段 產(chǎn)生新的垃圾(并發(fā)階段和應(yīng)用程序一同運行)昧诱;G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
-
Copy/Clean up所袁,多線程清除失活對象盏档,會有STW。G1將回收區(qū)域的存活對象拷貝到新區(qū)域燥爷,清除Remember Sets妆丘,并發(fā)清空回收區(qū)域并把它返回到空閑區(qū)域鏈表中。
-
復(fù)制/清除過程后局劲∩准穑回收區(qū)域的活性對象已經(jīng)被集中回收到深藍色和深綠色區(qū)域。
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #開啟
-XX:MaxGCPauseMillis =50 #暫停時間目標(biāo)
-XX:GCPauseIntervalMillis =200 #暫停間隔目標(biāo)
-XX:+G1YoungGenSize=512m #年輕代大小
-XX:SurvivorRatio=6 #幸存區(qū)比例
什么時候回收
Minor GC觸發(fā)
- Eden區(qū)域滿了鱼填,或者新創(chuàng)建的對象大小 > Eden所室┯校空間
- CMS設(shè)置了CMSScavengeBeforeRemark參數(shù),這樣在CMS的Remark之前會先做一次Minor GC來清理新生代苹丸,加速之后的Remark的速度愤惰。這樣整體的stop-the world時間反而短
- Full GC的時候會先觸發(fā)Minor GC
啥時候會觸發(fā)CMS GC?
CMS不等于Full GC赘理,很多人會認(rèn)為CMS肯定會引發(fā)Minor GC宦言。CMS是針對老年代的GC策略,原則上它不會去清理新生代商模,只有設(shè)置CMSScavengeBeforeRemark優(yōu)化時奠旺,或者是concurrent mode failure的時候才會去做Minor GC
1蜘澜、舊生代或者持久代已經(jīng)使用的空間達到設(shè)定的百分比時(CMSInitiatingOccupancyFraction這個設(shè)置old區(qū),perm區(qū)也可以設(shè)置)响疚;
2鄙信、JVM自動觸發(fā)(JVM的動態(tài)策略,也就是悲觀策略)(基于之前GC的頻率以及舊生代的增長趨勢來評估決定什么時候開始執(zhí)行)忿晕,如果不希望JVM自行決定装诡,可以通過-XX:UseCMSInitiatingOccupancyOnly=true來制定;
3践盼、設(shè)置了 -XX:CMSClassUnloadingEnabled 這個則考慮Perm區(qū)鸦采;
啥時候會觸發(fā)Full GC?
一咕幻、舊生代空間不足:java.lang.outOfMemoryError:java heap space渔伯;
二、Perm空間滿:java.lang.outOfMemoryError:PermGen space谅河;
三咱旱、CMS GC時出現(xiàn)promotion failed 和concurrent mode failure(Concurrent mode failure發(fā)生的原因一般是CMS正在進行确丢,但是由于old區(qū)內(nèi)存不足绷耍,需要盡快回收old區(qū)里面的死的java對象,這個時候foreground gc需要被觸發(fā)鲜侥,停止所有的java線程褂始,同時終止CMS,直接進行MSC描函。)崎苗;
四、統(tǒng)計得到的minor GC晉升到舊生代的平均大小大于舊生代的剩余空間舀寓;
五胆数、主動觸發(fā)Full GC(執(zhí)行jmap -histo:live [pid])來避免碎片問題;
六互墓、調(diào)用System.gc時必尼,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行,-XX:+DisableExplicitGC 禁用System.gc()調(diào)用
GC策略選擇總結(jié)
jvm有client和server兩種模式篡撵,這兩種模式的gc默認(rèn)方式是不同的:
client模式下判莉,新生代選擇的是串行g(shù)c,舊生代選擇的是串行g(shù)c
server模式下育谬,新生代選擇的是并行回收gc券盅,舊生代選擇的是并行g(shù)c
一般來說我們系統(tǒng)應(yīng)用選擇有兩種方式:吞吐量優(yōu)先和暫停時間優(yōu)先,對于吞吐量優(yōu)先的采用server默認(rèn)的并行g(shù)c方式膛檀,對于暫停時間優(yōu)先的選用并發(fā)gc(CMS)方式锰镀。
監(jiān)控與調(diào)優(yōu)
GC日志
-XX:+PrintGC 輸出GC日志
-XX:+PrintGCDetails 輸出GC的詳細日志
-XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準(zhǔn)時間的形式)
-XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式娘侍,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在進行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的輸出路徑
-verbose.gc開關(guān)可顯示GC的操作內(nèi)容。
打開它互站,可以顯示最忙和最空閑收集行為發(fā)生的時間私蕾、收集前后的內(nèi)存大小、收集需要的時間等
-XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps
使用-XX:+PrintGCTimeStamps可以將時間和日期也加到GC日志中胡桃。表示自JVM啟動至今的時間戳?xí)惶砑拥矫恳恍兄胁劝取@尤缦拢?/p>
1 0.185: [GC 66048K->53077K(251392K), 0.0977580 secs]
2 0.323: [GC 119125K->114661K(317440K), 0.1448850 secs]
3 0.603: [GC 246757K->243133K(375296K), 0.2860800 secs]
如果指定了-XX:+PrintGCDateStamps,每一行就添加上了絕對的日期和時間翠胰。
1 2014-01-03T12:08:38.102-0100: [GC 66048K->53077K(251392K), 0.0959470 secs]
2 2014-01-03T12:08:38.239-0100: [GC 119125K->114661K(317440K), 0.1421720 secs]
3 2014-01-03T12:08:38.513-0100: [GC 246757K->243133K(375296K), 0.2761000 secs]
如果需要也可以同時使用兩個參數(shù)容贝。推薦同時使用這兩個參數(shù),因為這樣在關(guān)聯(lián)不同來源的GC日志時很有幫助
每一種收集器的日志形式都是由它們自身的實現(xiàn)所決定的之景,換而言之斤富,每個收集器的日志格式都可以不一樣。
但虛擬機設(shè)計者為了方便用戶閱讀锻狗,將各個收集器的日志都維持一定的共性满力,
例如以下兩段典型的GC日志:
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K),
[Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
最前面的數(shù)字“33.125:”和“100.667:”代表了GC發(fā)生的時間,這個數(shù)字的含義是從Java虛擬機啟動以來經(jīng)過的秒數(shù)轻纪。
GC日志開頭的“[GC”和“[Full GC”說明了這次垃圾收集的停頓類型油额,
而不是用來區(qū)分新生代GC還是老年代GC的。
如果有“Full”刻帚,說明這次GC是發(fā)生了Stop-The-World的潦嘶,
例如下面這段新生代收集器ParNew的日志也會出現(xiàn)“[Full GC”(這一般是因為出現(xiàn)了分配擔(dān)保失敗之類的問題,所以才導(dǎo)致STW)崇众。
如果是調(diào)用System.gc()方法所觸發(fā)的收集掂僵,那么在這里將顯示“[Full GC (System)”。
[Full GC 283.736: [ParNew: 261599K->261599K(261952K), 0.0000288 secs]
接下來的“[DefNew”顷歌、“[Tenured”锰蓬、“[Perm”表示GC發(fā)生的區(qū)域,這里顯示的區(qū)域名稱與使用的GC收集器是密切相關(guān)的
例如上面樣例所使用的Serial收集器中的新生代名為“Default New Generation”眯漩,所以顯示的是“[DefNew”芹扭。
如果是ParNew收集器,新生代名稱就會變?yōu)椤埃跴arNew”坤塞,意為“Parallel New Generation”冯勉。
如果采用Parallel Scavenge收集器,那它配套的新生代稱為“PSYoungGen”摹芙,老年代和永久代同理灼狰,名稱也是由收集器決定的。
后面方括號內(nèi)部的“3324K->152K(3712K)”含義是“GC前該內(nèi)存區(qū)域已使用容量-> GC后該內(nèi)存區(qū)域已使用容量 (該內(nèi)存區(qū)域總?cè)萘?”浮禾。
而在方括號之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量 -> GC后Java堆已使用容量 (Java堆總?cè)萘?”交胚。
再往后份汗,“0.0025925 secs”表示該內(nèi)存區(qū)域GC所占用的時間,單位是秒蝴簇。
有的收集器會給出更具體的時間數(shù)據(jù)
如“[Times: user=0.01 sys=0.00杯活, real=0.02 secs]”,
這里面的user熬词、sys和real與Linux的time命令所輸出的時間含義一致旁钧,分別代表用戶態(tài)消耗的CPU時間、內(nèi)核態(tài)消耗的CPU事件和操作從開始到結(jié)束所經(jīng)過的墻鐘時間(Wall Clock Time)互拾。
CPU時間與墻鐘時間的區(qū)別是歪今,墻鐘時間包括各種非運算的等待耗時,例如等待磁盤I/O颜矿、等待線程阻塞寄猩,而CPU時間不包括這些耗時,但當(dāng)系統(tǒng)有多CPU或者多核的話骑疆,多線程操作會疊加這些CPU時間田篇,所以讀者看到user或sys時間超過real時間是完全正常的。
分析工具
可以使用一些離線的工具來對GC日志進行分析
比如sun的gchisto( https://java.net/projects/gchisto)
gcviewer( https://github.com/chewiebug/GCViewer )箍铭,
這些都是開源的工具泊柬,用戶可以直接通過版本控制工具下載其源碼,進行離線分析
打印JVM參數(shù)
-XX:+PrintFlagsFinal and -XX:+PrintFlagsInitial
[Global flags]
uintx AdaptivePermSizeWeight = 20 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}[...]
uintx YoungGenerationSizeSupplementDecay = 8 {product}
uintx YoungPLABSize = 4096 {product}
bool ZeroTLAB = false {product}
intx hashCode = 0 {product}
表格的每一行包括五列坡疼,來表示一個XX參數(shù)彬呻。第一列表示參數(shù)的數(shù)據(jù)類型衣陶,第二列是名稱柄瑰,第四列為值,第五列是參數(shù)的類別剪况。第三列”=”表示第四列是參數(shù)的默認(rèn)值教沾,而”:=” 表明了參數(shù)被用戶或者JVM賦值了。
-XX:+PrintCommandLineFlags
這個參數(shù)讓JVM打印出那些已經(jīng)被用戶或者JVM設(shè)置過的詳細的XX參數(shù)的名稱和值译断。
換句話說授翻,它列舉出 -XX:+PrintFlagsFinal的結(jié)果中第三列有":="的參數(shù)。
以這種方式孙咪,我們可以用-XX:+PrintCommandLineFlags作為快捷方式來查看修改過的參數(shù)
監(jiān)控jvm
使用自帶工具就行堪唐,jstat,jmap,jstack
優(yōu)化
- 選擇合適的GC collector
- 整個JVM heap的大小
- young generation在整個JVM heap中所占的比重
參數(shù)實例
public static void main(String[] args) throws InterruptedException{
//通過allocateDirect分配128MB直接內(nèi)存
ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);
TimeUnit.SECONDS.sleep(10);
System.out.println("ok");
}
測試用例1:設(shè)置JVM參數(shù)-Xmx100m,運行異常翎蹈,因為如果沒設(shè)置-XX:MaxDirectMemorySize淮菠,則默認(rèn)與-Xmx參數(shù)值相同,分配128M直接內(nèi)存超出限制范圍
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306)
at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)
為了避免Perm區(qū)滿引起的full gc荤堪,建議開啟CMS回收Perm區(qū)選項:
+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled
默認(rèn)CMS是在tenured generation沾滿68%的時候開始進行CMS收集合陵,如果你的年老代增長不是那么快枢赔,并且希望降低CMS次數(shù)的話,可以適當(dāng)調(diào)高此值:
-XX:CMSInitiatingOccupancyFraction=80
遇到兩種fail引起full gc:
Prommotion failed和Concurrent mode failed時:
promotion failed是在進行Minor GC時拥知,survivor space放不下踏拜、
對象只能放入舊生代,而此時old gen 的碎片太多為進行過內(nèi)存重組和壓縮低剔,無法提供一塊較大的烈评、連續(xù)的內(nèi)存空間存放來自新生代對象
Prommotion failed的日志輸出大概是這樣:
42576.951: [ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]
42576.951: [CMS: 1139969K->1120688K( 166784K), 9.2214860 secs] 1458785K->1120688K(2520704K), 9.4584090 secs]
因為
解決這個問題的辦法有兩種完全相反的傾向:增大救助空間、增大年老代或者去掉救助空間簇爆。
解決方法可以通過設(shè)置參數(shù)
-XX:+UseCMSCompactAtFullCollection(打開對年老代的壓縮)
-XX:CMSFullGCsBeforeCompaction(設(shè)置運行多少次FULL GC以后對內(nèi)存空間進行壓縮硕糊、整理)
直接關(guān)了servivor空間
-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0
concurrent mode failure
發(fā)生在當(dāng)CMS已經(jīng)在工作并處于concurrent階段中,而Java堆的內(nèi)存不夠用需要做major GC(full GC)的時候蕊唐。換句話說屋摔,old gen內(nèi)存的消耗速度比CMS的收集速度要高,CMS收集器跟不上分配速度的時候會發(fā)生concurrent mode failure
Concurrent mode failed的日志大概是這樣的:
(concurrent mode failure): 1228795K->1228598K(1228800K), 7.6748280 secs] 1911483K->1681165K(1911488K),
[CMS Perm : 225407K->225394K(262144K)], 7.6751800 secs]
避免這個現(xiàn)象的產(chǎn)生就是調(diào)小-XX:CMSInitiatingOccupancyFraction參數(shù)的值替梨,
讓CMS更早更頻繁的觸發(fā)钓试,降低年老代被沾滿的可能。
full gc頻繁說明old區(qū)很快滿了副瀑。
如果是一次full gc后弓熏,剩余對象不多。那么說明你eden區(qū)設(shè)置太小糠睡,導(dǎo)致短生命周期的對象進入了old區(qū)
如果一次full gc后挽鞠,old區(qū)回收率不大,那么說明old區(qū)太小
已知虛擬機的一些參數(shù)設(shè)置如下:
-Xms:1G狈孔;
-Xmx:2G信认;
-Xmn:500M;
-XX:MaxPermSize:64M均抽;
-XX:+UseConcMarkSweepGC嫁赏;
-XX:SurvivorRatio=3;
求Eden區(qū)域的大杏突印潦蝇?
分析這是網(wǎng)易2016年在線筆試題中的一道選擇題。
先分析一下里面各個參數(shù)的含義:
-Xms:1G 深寥, 就是說初始堆大小為1G
-Xmx:2G 攘乒, 就是說最大堆大小為2G
-Xmn:500M ,就是說年輕代大小是500M(包括一個Eden和兩個Survivor)
-XX:MaxPermSize:64M 惋鹅, 就是說設(shè)置持久代最大值為64M
-XX:+UseConcMarkSweepGC 则酝, 就是說使用使用CMS內(nèi)存收集算法
-XX:SurvivorRatio=3 , 就是說Eden區(qū)與Survivor區(qū)的大小比值為3:1:1
題目中所問的Eden區(qū)的大小是指年輕代的大小负饲,直接根據(jù)-Xmn:500M和-XX:SurvivorRatio=3可以直接計算得出
解500M*(3/(3+1+1))
=500M*(3/5)
=500M*0.6
=300M
所以Eden區(qū)域的大小為300M堤魁。
參考資料
http://yinwufeng.iteye.com/blog/2157787
http://itindex.net/detail/47030-cms-gc-%E9%97%AE%E9%A2%98
http://www.cnblogs.com/ityouknow/p/5614961.html
歡迎關(guān)注