一、什么是GC
JVM GC是:JVM的垃圾回收算法,現在的JVM基本采用分代收集,Young區收集頻繁,Old區收集較少,Perm(永久代)基本不回收;JVM進行GC時大部分是對新生代的回收,少量的全局回收。
GC按照作用的區域分為:
Minor GC:作用於新生代
Major GC(Full GC):作用於老年代,偶爾也會回收老年代和永久代。
二、如何定位垃圾
1、引用計數法
引用計數算法很簡單,它實際上是通過在對象頭中分配一個空間來保存該對象被引用的次數。如果該對象被其它對象引用,則它的引用計數加一,如果刪除對該對象的引用,那么它的引用計數就減一,當該對象的引用計數為0時,那么該對象就會被標記為垃圾對象。
第10行 str引用了“ABC” 則“ABC”的計算器等於1。第11行str釋放了該引用,所以“ABC”的計數器就減一。
優點:實現簡單,判定高效,可以很好解決大部分場景的問題。
缺點:
- 很難解決對象之間相互循環引用的問題,當兩個對象不再被訪問時,因為相互引用對方,導致引用計數不為0;
- 開銷較大,頻繁且大量的引用變化,帶來大量的額外運算;主流的JVM都沒有選用引用計數算法來管理內存;
2、可達性分析法(根搜索算法)
可達性分析法:通過一系列"GC Roots"對象作為起始點,開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,認為該對象不可達,則證明該對象是不可用的;
優點:
- 更加精確和嚴謹,可以分析出循環數據結構相互引用的情況;
- 主流的調用程序語言(Java、C#等)在主流的實現中,都是通過可達性分析來判定對象是否存活的。
缺點:
- 實現比較復雜;
- 需要分析大量數據,消耗大量時間;
- 分析過程需要GC停頓(引用關系不能發生變化),即停頓所有Java執行線程(稱為"Stop The World",是垃圾回收重點關注的問題);
哪些對象可以作為GC ROOT對象(GCRoot 可以是一個也可以是多個):
- 1、虛擬機棧(棧楨中的局部變量區,也叫局部變量表)中引用的變量
- 2、方法區中類的靜態屬性引用的對象
- 3、方法區中常量引用的對象
- 4、本地方法棧中JNI(Native)引用的對象
三、垃圾回收算法
1、標記-復制:它將可用內存容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊用完之后,就將還存活的對象復制到另外一塊上面,然后在把已使用過的內存空間一次理掉。
JVM實現原理:Survivor區,一塊叫From,一塊叫To,對象存在Eden和From塊。當進行GC時,Eden存活的對象全移到To塊,而From中,存活的對象按年齡值確定去向,當達到一定值(年齡閾值,通過-XX:MaxTenuringThreshold可設置,默認=15)的對象會移到年老代中,沒有達到值的復制到To區,然后直接清空Eden和From。之后,From和To交換角色,新的From即為原來的To塊,新的To塊即為原來的From塊,且新的Form塊中對象年齡加1.
優點:內存分配時也不用考慮內存碎片等問題;實現簡單,運行高效;可以利用指針碰撞(bump-the-pointer)實現快速內存分配
缺點:
- 空間浪費:可用內存縮減為原來的一半,太過浪費(解決:可以改良,不按1:1比例划分);
- 效率隨對象存活率升高而變低:當對象存活率較高時,需要進行較多復制操作(對象的引用地址需要復制),效率將會變低,所以該算法不適合對象存活率較高的場景或者區域。
應用場景:
- 現在商業JVM都采用這種算法(通過改良缺點1)來回收新生代;
- 如Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1(從局部看)。
2、標記-清除:首先標記出需要回收的對象,標記完成之后統一清除對象。
標記:從根集合開始掃描,標記存貨的對象
清除:掃描整個堆內存空間,回收未被標記的對象,使用free-list記錄可以使用的區域
優點:基於最基礎的可達性分析算法,它是最基礎的收集算法;而后續的收集算法都是基於這種思路並對其不足進行改進得到的;
缺點:
- 效率問題:標記和清除都需要掃描,兩個過程的效率都不高;
- 空間問題:標記清除后會產生大量不連續的內存碎片,這會導致分配大內存對象時,無法找到足夠的連續內存,從而需要提前觸發另一次垃圾收集動作。
- stop-the-Word:在標記時需要暫停JVM用戶進程
應用場景:針對老年代
3、標記-整理
標記-整理:標記操作和“標記-清理”算法一致,后續操作不只是直接清理對象,而是在清理無用對象前,先將存活的對象都向一端移動,並更新引用其對象的指針,然后直接清理掉端邊界以外的內存。
標記:和“標記-清理”算法一致
整理:掃描整個堆內存空間,將存活的對象都向一端移動,並更新引用其對象的指針,然后直接清理掉邊界以外的內存。整理的目的就是整合零散分布的空間碎片為一個連續的空間。
優點:
- 不會像復制算法,效率隨對象存活率升高而變低
- 不會像標記-清除算法,產生內存碎片,因為清除前,進行了整理,存活對象都集中到空間一側;
缺點:主要是效率問題:除像標記-清除算法的標記過程外,還多了需要整理的過程,效率更低;
應用場景:回收老年代;
4、標記-清除-整理(Mark-Sweep-Compact)
該算法是標記清除和標記整理的結合,標記-清除會產生碎片,標記-整理每次都進行整理效率不高;標記-清楚-整理 是如果老年代內存中沒有一塊連續續的空間可以存放將要進入對象,就進行整理;如果內存中的空間可以存放將要進入的對象,就進行標記-清除,這樣就節省了整理的步驟可以提高效率。總結一句話:不是所有的時候都需要整理的,因為整理也付出代價。主要應用於老年代
總結: 沒有最好的算法,只有最合適的引用場景
下一節:GC算法的實現