JVM垃圾回收機制與算法

    JVM內存由幾個部分組成:堆、方法區、棧、程序計數器、本地方法棧
    JVM垃圾回收僅針對公共內存區域,即:堆和方法區進行,因爲只有這兩個區域在運行時才能知道需要創建些對象,其內存分配和回收都是動態的。

一、垃圾回收策略
    1.1分代管理
        將堆和方法區按照對象不同年齡進行分代:
          (Young Generation and Old Generation)堆中會頻繁創建對象,基於一種分代的思想,按照對象存活時間將堆劃分爲新生代和舊生代兩部分,並不是一次垃圾回收新生代存活的對象就放入舊生代,
          而是要經過幾次GC後還存活的對象,才放入舊生代,所以把新生代再次劃分爲Eden區和兩個Survivor區,讓對象創建在Eden區,然後在兩個Survivor之間反覆複製,
          最後仍然存活的對象才複製到舊生代中。
          
          (Permanent Generation)方法區存放的是常量、加載的字節碼文件信息等,信息相對穩定。因爲不會頻繁創建對象,所以不需要分代,直接GC即可。
          
          新生代:
            1.所有新對象創建發生在Eden區,Eden區滿後觸發新生代上的minor GC,將Eden區和非空閒Survivor區存活對象複製到另一個空閒的Survivor區中。
            2.永遠保證一個Survivor是空的,新生代minor GC就是兩個Survivor區之間相互複製存活對象,直到Survivor區滿爲止。
            
          舊生代:
            1.Eden區滿後觸發minor GC將存活對象複製到Survivor區,Survivor區滿後觸發minor GC將存活對象複製到舊生代。
            2.經過新生代的兩個Survivor之間多次複製,仍然存活下來的對象就是年齡相對比較老的,可以放入舊生代了,隨着時間推移,
            如果舊生代也滿了,將觸發Full GC,針對整個堆(包括新生代、舊生代和持久代)進行垃圾回收。
            
          持久代:
            存儲class類、常量、方法描述等,持久代如果滿,將觸發Full GC,回收廢棄常量和無用的類
            
    1.2垃圾回收
        要執行垃圾回收,關鍵在於兩點,一是檢測垃圾對象,二是釋放垃圾對象所佔用的空間。
        1.檢測垃圾對象
            1)引用計數法:
                對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1.
                只要對象A的引用計數器的值爲0,則對象A 就不可能再被使用。
                實現時只需要爲每個對象配置一個整型的計數器即可。
                但是引用計數器有一個嚴重的問題,即無法處理循環引用的情況。
                對象A中含有對象B的引用,對象B中含有對象A的引用。此時,對象A和B的引用計數器都不爲0.但是在系統中卻不存在任何第3個對象引用了A或B。
                即,A和B是應該被回收的垃圾對象,但由於垃圾對象間相互引用,從而垃圾回收器無法識別,引起內存泄漏。
                
            2)可達性分析:
                引用計數法無法檢測對象之間相互循環引用的問題,所以基本不用。垃圾回收中檢測垃圾對象主要還是“可達性分析”法。
                
                可達性分析算法:
                    通過一系列名爲“GC Root”的對象作爲起點,從這些節點向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),
                    當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所佔的內存。
                    所以JVM判斷對象需要存活的原則是:能夠被一個根對象到達的對象。
                    可達:對象A引用了對象B,即A到B可達。
                    
                GC Root對象集合:
                    a:Java虛擬機棧(棧幀中的本地變量表)中的引用對象。(當前棧幀的對象引用)
                    b:方法區中的類靜態屬性引用的對象。(static對象引用)
                    c:方法區中的常量引用的對象。(final對象引用)
                    d:本地方法棧中JNI本地方法的引用對象。
                
                除了堆之外,方法區中的“廢棄常量”和“無用的類”需要回收以保證永久代不會發生內存溢出,檢測方法區垃圾對象的方法:
                    1、判斷廢棄常量的方法(不再需要的常量):如果常量池中的某個常量沒有被任何引用所引用,則該常量是廢棄常量
                    2、判斷無用的類(不再需要的class文件):
                        1)該類的所有實例都已經被回收,即Java堆中不存在該類的實例對象
                        2)加載該類的類加載器已經被回收
                        3)該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法
                
                當持久代(方法區)滿時,將觸發Full GC,根據以上標準清除廢棄的常量和無用的類。
                
        2.釋放空間
            垃圾回收算法
                    1)標記-清除(Mark-Sweep):最基礎的垃圾回收算法
                        標記-清除算法分爲兩個階段:標記階段和清除階段。
                        標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。
                        標記-清除算法容易實現,但問題是,容易產生內存碎片,碎片太多可能會導致後續過程中需要爲大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。
                    
                    2)複製(Copying):
                        將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。
                        複製(Copying)算法實現簡單,運行高效且不容易產生內存碎片,但是能夠使用的內存縮減到原來的一半。
                        Copying算法的效率跟存活對象數目多少有很大的關係,如果存活對象很多,那麼Copying算法的效率將大大降低。
                        
                    3)標記-整理(Mark-Compact):
                        該算法標記階段和標記-清除(Mark-Sweep)一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。
                        
                    4)分代(Generation Collection),藉助前面三種算法實現:
                        分代收集算法是目前大部分JVM的垃圾收集器採用的算法。
                        核心思想是根據對象存活的生命週期將內存劃分爲若干個不同的區域。一般情況下,將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),
                            老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那麼就可以根據不同代的特點採取最適合的收集算法。
                            
                        目前大部分垃圾收集器對於新生代都採取Copying算法,因爲新生代中每次垃圾回收都要回收大部分對象,也就是說需要複製的操作次數比較少,但是實際中並不是按照1:1的比例來劃分新生代的空間,
                            一般來說是將新生代劃分爲一塊較大的Eden區和兩塊較小的Survivor區,每次使用Eden區和其中一塊Survivor區,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor區中,
                            然後清理掉Eden和剛纔使用過的Survivor區。
                                
                        而由於老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。
                            
   二、典型的垃圾收集器
        HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶可以根據自己的需求組合出各個年代使用的收集器。
        1、Serial/Serial Old(串行收集器)
            Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,並且在它進行垃圾收集時,必須暫停所有用戶線程。
            Serial收集器是針對新生代的收集器,採用的是Copying算法,Serial Old收集器是針對老年代的收集器,採用的是Mark-Compact算法。
            優點是實現簡單高效,缺點是會給用戶帶來停頓。
            
        2、ParNew
            ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。
            
        3、Parallel Scavenge
            Parallel Scavenge收集器是一個新生代的多線程收集器(並行收集器),它在回收期間不需要暫停其他用戶線程,其採用的是Copying複製算法,
                該收集器與前兩個收集器有所不同,它主要是爲了達到一個可控的吞吐量。
                
        4、Parallel  Old
            Parallel Old是Parallel Scavenge收集器的老年代版本(並行收集器),使用多線程和Mark-Compact(標記-整理)算法。
        
        5、CMS
            CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓爲目標的收集器,它是一種併發收集器,採用的是Mark-Sweep(標記-清除)算法。
            (並行收集器:多條垃圾收集線程並行工作,而用戶線程仍處於等待狀態)
            (併發收集器:垃圾收集線程與用戶線程一段時間內同時工作(不是並行,而是交替執行))
            
        6、G1
            G1收集器是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。
            G1收集器是一款並行與併發收集器,並且它能建立可預測的停頓時間模型。
        
                        
                        
                    
            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章