【JVM虛擬機】(2)---GC 算法與種類


GC 算法與種類

  對於垃圾收集(GC), 我們需要考慮三件事情:哪些內存需要回收?如何判斷是垃圾對象?垃圾回收算法有哪些?

一、GC的工作區域

1、不是GC的工作區域

    (1)程序計數器、虛擬機棧和本地方法棧三個區域是線程私有的,隨線程生而生,隨線程滅而滅;

    (2)棧中的棧幀隨着方法的進入和退出而進行入棧和出棧操作,每個棧幀中分配多少內存基本上是在類結構確定下來時就已知的,因此這幾個區域的內存分配和回收都具有確定性。

在這幾個區域不需要過多考慮回收的問題,因為方法結束或線程結束時,內存自然就跟隨着回收了。

2、GC的工作區域(哪些內存需要GC回收?)

(1)垃圾回收重點關注的是堆和方法區部分的內存。

       因為一個接口中的多個實現類需要的內存可能不一樣,一個方法的多個分支需要的內存也可能不一樣,我們只有在程序處於運行期間才能知道會創建哪些對象,這部分內存的分

配和回收都是動態的,所以垃圾回收器所關注的主要是這部分的內存。

 

二、垃圾對象的判定

Java堆中存放着幾乎所有的對象實例,垃圾收集器對堆中的對象進行回收前,要先確定這些對象是否還有用,哪些還活着。對象死去的時候才需要回收。

1、引用計數法

      引用計數法的邏輯是:在堆中存儲對象時,在對象頭處維護一個counter計數器,如果一個對象增加了一個引用與之相連,則將counter++。

如果一個引用關系失效則counter–。如果一個對象的counter變為0,則說明該對象已經被廢棄,不處於存活狀態。

優點

    1)可即刻回收垃圾,每個對象都知道自己的被引用數,當counter為0時,對象就會把自己作為空閑空間連接到空閑鏈表,也就是在對象變成垃圾的同時就會被回收.

    2)最大暫停時間短,每次通過指向mutator生成垃圾時,這部分垃圾都會被回收,大幅削減了mutator的最大暫停時間。

缺點

    1)引用和去引用伴隨加法和減法,影響性能

    2)很難處理循環引用

2、可達性分析算法

      這種算法的基本思路是通過一系列名為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,就證明此對象是不可用的

Java語言是通過可達性分析算法來判斷對象是否存活的。

在Java語言里,可作為GC Roots的對象包括下面幾種:

      (1)虛擬機棧(棧幀中的本地變量表)中引用的對象。

      (2)方法區中的類靜態屬性引用的對象。

      (3)方法區中的常量引用的對象。

      (4)本地方法棧中JNI(Native方法)的引用對象。

 

 三、垃圾回收算法

1、標記-清除算法

簡單來說有兩個步驟:標記、清除。

  (1). 標記階段:找到所有可訪問的對象,做個標記

  (2). 清除階段:遍歷堆,把未被標記的對象回收

 缺  點

   (1)因為涉及大量的內存遍歷工作,所以執行性能較低,這也會導致“stop the world”時間較長,java程序吞吐量降低;

   (2)對象被清除之后,被清除的對象留下內存的空缺位置會造成內存不連續,空間浪費。

2、標記整理(壓縮)算法 

標記-整理算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上做了一些優化。

     (1)、標記階段:它的第一個階段與標記/清除算法是一模一樣的。

     (2)、整理階段:移動所有存活的對象,且按照內存地址次序依次排列,然后將末端內存地址以后的內存全部回收。

       上圖中可以看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當我們需要給新對象分配內存時,JVM只需要持有一個內存的起始地址即可,這比維護一個空閑

列表顯然少了許多開銷。

優點

      標記/整理算法不僅可以彌補標記/清除算法當中,內存區域分散的缺點,也消除了復制算法當中,內存減半的高額代價。

缺點

     標記/整理算法唯一的缺點就是效率也不高。不僅要標記所有存活對象,還要整理所有存活對象的引用地址。從效率上來說,標記/整理算法要低於復制算法。 

3、復制算法

      復制算法簡單來說就是把內存一分為二,但只使用其中一份,在垃圾回收時,將正在使用的那份內存中存活的對象復制到另一份空白的內存中,最后將正在使用的內存空間的對象清除,完成垃圾回收。

優點
       復制算法使得每次都只對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點
       復制算法的代價是將內存縮小為原來的一半,這個太要命了。

注意(重要)

       現在的虛擬機使用復制算法來進行新生代的內存回收。因為在新生代中絕大多數的對象都是“朝生夕亡”,所以不需要將整個內存分為兩個部分,而是分為三個部分,一塊為Eden(伊面區)和兩塊較小的

Survivor(幸存區)空間(默認比例->8:1:1)。每次使用Eden和其中的一塊Survivor,垃圾回收時候將上述兩塊中存活的對象復制到另外一塊Survivor上,同時清理上述Eden和Survivor。所以每次新生代就可以使用90%

的內存。只有10%的內存是浪費的。(不能保證每次新生代都少於10%的對象存活,當在垃圾回收復制時候如果一塊Survivor不夠時候,需要老年代來分擔,大對象直接進入老年代) 

總的來講:復制算法不適用於存活對象較多的場合,如老年代(復制算法適合做新生代的GC)

 4、三種算法總結

相同點

      (1)三個算法都基於根搜索算法去判斷一個對象是否應該被回收,而支撐根搜索算法可以正常工作的理論依據,就是語法中變量作用域的相關內容。

      (2)在GC線程開啟時,或者說GC過程開始時,它們都要暫停應用程序(stop the world)。

區別

三種算法比較:

       效率:復制算法>標記-整理算法>標記-清除算法;

       內存整齊度:復制算法=標記-整理算法>標記-清除算法

       內存利用率:標記-整理算法=標記-清除算法>復制算法

 5、分代收集算法

      首先這不是一種新算法,它是一種思想。現在使用的Java虛擬機並不是只是使用一種內存回收機制,而是分代收集的算法。就是將內存根據對象存活的周期划分為幾塊。一般是把堆分為新生代、和老年代。短命對

象存放在新生代中,長命對象放在老年代中。

    這個圖是我拷貝來的,但要記住java8以后,已經沒有永久區了,之前永久區存放的東西基本上放到了元空間中。

對於不同的代,采用不同的收集算法:

       新生代:由於存活的對象相對比較少,因此可以采用復制算法該算法效率比較快。

       老年代:由於存活的對象比較多哈,可以采用標記-清除算法或是標記-整理算法。

 

參考

   1、Java垃圾回收(GC)機制詳解

   2、深入理解JVM:Java垃圾收集

 

 想太多,做太少,中間的落差就是煩惱。想沒有煩惱,要么別想,要么多做。少校【16】 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM