3.垃圾回收器
3.1.引用計數法
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。
但是,至少主流的Java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間相互循環引用的問題。
3.2.可達性分析算法
這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的
在
虛擬機棧(棧幀中的本地變量表)中引用的對象。
方法區中類靜態屬性引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI(即一般說的Native方法)引用的對象。
對象死亡過程:
對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視為“沒有必要執行”。這個對象被判定為有必要執行finalize()方法,那麽這個對象將會放置在一個叫做F-Queue的隊列之中,執行finalize()方法但不保證成功。GC將對F-Queue中的對象進行第二次小規模的標記,然後回收
方法區回收(永久代):
兩部分內容:
回收廢棄常量與回收Java堆中的對象
廢棄常量:假如一個字符串“abc”已經進入了常量池中,但是當前系統沒有任何一個String對象是叫做“abc”的,換句話說,就是沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發生內存回收,而且必要的話,這個“abc”常量就會被系統清理出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。
無用類:該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例;加載該類的ClassLoader已經被回收;該類對應的java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
3.3.垃圾回收算法
最基礎的收集算法-標記清除法:算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象。後續的收集算法都是基於這種思路並對其不足進行改進而得到的。它的主要不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除之後會產生大量不連續的內存碎片
復制算法:它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然後再把已使用過的內存空間一次清理掉。實現簡單,運行高效。只是這種算法的代價是將內存縮小為了原來的一半
現在的商業虛擬機都采用這種收集算法來回收新生代,每次使用Eden和其中一塊Survivor,當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間
復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
標記-整理算法:
標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。老年代使用這種算法
分代收集算法:
當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法並沒有什麽新的思想,只是根據對象存活周期的不同將內存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。
3.4.垃圾回收器種類
總體圖(jdk1.7支持的垃圾收集器):
如果兩個收集器之間存在連線,就說明它們可以搭配使用。虛擬機所處的區域,則表示它是屬於新生代收集器還是老年代收集器
serial收集器:單線程的收集器,它的“單線程”的意義並不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。是虛擬機運行在Client模式下的默認新生代收集器
ParNew收集器:Serial收集器的多線程版本,是許多運行在Server模式下的虛擬機中首選的新生代收集器,其中有一個與性能無關但很重要的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作。
Parallel Scavenge收集器:也經常稱為“吞吐量優先”收集器,即是使用復制算法,又是並行的多線程收集器……看上去和ParNew都一樣,特點是它的關註點與其他收集器不同,目標則是達到一個可控制的吞吐量(Throughput),所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間),也經常稱為“吞吐量優先”收集器
參數:
-XX:MaxGCPauseMillis 控制最大垃圾收集停頓時間,停頓時間縮短是以犧牲吞吐量和新生代空間來換取的:系統把新生代調小一些,收集300MB新生代肯定比收集500MB快吧,這也直接導致垃圾收集發生得更頻繁一些,原來10秒收集一次、每次停頓100毫秒,現在變成5秒收集一次、每次停頓70毫秒。停頓時間的確在下降,但吞吐量也降下來了。
GCTimeRatio參數的值應當是一個大於0且小於100的整數,也就是垃圾收集時間占總時間的比率,相當於是吞吐量的倒數。如果把此參數設置為19,那允許的最大GC時間就占總時間的5%(即1 /(1+19)),默認值為99,就是允許最大1%(即1 /(1+99))的垃圾收集時間。
-XX:+UseAdaptiveSizePolicy值得關註。這是一個開關參數,當這個參數打開之後,就不需要手工指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱為GC自適應的調節策略(GC Ergonomics)
Serial Old收集器:
是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“標記-整理”算法。這個收集器的主要意義也是在於給Client模式下的虛擬機使用。如果在Server模式下,那麽它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另一種用途就是作為CMS收集器的後備預案,在並發收集發生Concurrent Mode Failure時使用。
Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。在註重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。
CMS收集器:收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。是基於“標記—清除”算法實現的。
CMS收集器對CPU資源非常敏感,CMS默認啟動的回收線程數是(CPU數量+3)/ 4,也就是當CPU在4個以上時,並發回收時垃圾收集線程不少於25%的CPU資源,並且隨著CPU數量的增加而下降。但是當CPU不足4個(譬如2個)時,CMS對用戶程序的影響就可能變得很大,如果本來CPU負載就比較大,還分出一半的運算能力去執行收集器線程,
CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS並發清理階段用戶線程還在運行著,伴隨程序運行自然就還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱為“浮動垃圾”。
CMS是一款基於“標記—清除”算法實現的收集器,如果讀者對前面這種算法介紹還有印象的話,就可能想到這意味著收集結束時會有大量空間碎片產生。空間碎片過多時,將會給大對象分配帶來很大麻煩,往往會出現老年代還有很大空間剩余,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關參數(默認就是開啟的),用於在CMS收集器頂不住要進行FullGC時開啟內存碎片的合並整理過程,內存整理的過程是無法並發的,空間碎片問題沒有了,但停頓時間不得不變長。虛擬機設計者還提供了另外一個參數-XX:CMSFullGCsBeforeCompaction,這個參數是用於設置執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的(默認值為0,表示每次進入Full GC時都進行碎片整理)。
G1收集器:當今收集器技術發展的最前沿成果之一,是一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發布的CMS收集器。與其他GC收集器相比,G1具備如下特點:並行與並發;分代收集;空間整合;可預測的停頓,但是出現時間較短,有待商榷
3.4.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]
GC日誌開頭的“[GC”和“[Full GC”說明了這次垃圾收集的停頓類型,而不是用來區分新生代GC還是老年代GC的。如果有“Full”,說明這次GC是發生了Stop-The-World的,例如下面這段新生代收集器ParNew的日誌也會出現“[Full GC”(這一般是因為出現了分配擔保失敗之類的問題,所以才導致STW)。如果是調用System.gc()方法所觸發的收集,那麽在這裏將顯示“[Full GC (System)”接下來的“[DefNew”、“[Tenured”、“[Perm”表示GC發生的區域,這裏顯示的區域名稱與使用的GC收集器是密切相關的,例如上面樣例所使用的Serial收集器中的新生代名為“Default New Generation”,所以顯示的是“[DefNew”。如果是ParNew收集器,新生代名稱就會為“[ParNew”,意為“Parallel New Generation”。如果采用Parallel Scavenge收集器,那它配套的新生代稱為“PSYoungGen”,老年代和永久代同理,名稱也是由收集器決定的。後面方括號內部的“3324K->152K(3712K)”含義是“GC前該內存區域已使用容量-> GC後該內存區域已使用容量 (該內存區域總容量)”。而在方括號之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量 -> GC後Java堆已使用容量 (Java堆總容量)”。
再往後,“0.0025925 secs”表示該內存區域GC所占用的時間,單位是秒。
名稱對應關系:
3.5.垃圾收集器參數總結
本文出自 “13165699” 博客,請務必保留此出處http://13175699.blog.51cto.com/13165699/1954469
3.垃圾回收器