java 垃圾回收機制

垃圾收集GC(Garbage Collection)是Java語言的核心技術之一, 在Java中,程序員不須要去關心內存動態分配和垃圾回收的問題,這一切都交給了JVM來處理。java

一. jvm的內存結構程序員

垃圾回收都是基於內存去回收的,所以,先要對內存結構有一個大概的瞭解算法

 

 

 Java內存運行時區域大概分了三部分多線程

  • 其中PC寄存器、java虛擬機棧、本地方法棧3個區域是全部線程獨有的一塊區域,隨線程而生,隨線程而滅。棧中的棧幀隨着方法的進入和退出而有條不紊地執行着入棧和出棧操做。每個棧幀中分配多少內存基本上是在類結構肯定下來時就已知的,所以這幾個區域的內存分配和回收都具有肯定性,在這幾個區域內就不須要過多考慮回收的問題,由於方法結束或者線程結束,內存天然就跟隨着回收了。
  • 而Java堆和方法區則不同,一個接口中的多個實現類須要的內存可能不同,一個方法中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,只有在程序處於運行期間時才能知道會建立哪些對象,這部份內存的分配和回收是動態的,垃圾收集關注的是這部分的內存。

. 內存分配

在解垃圾回收以前,得先了解JVM是怎麼分配內存的,而後識別哪些內存是垃圾須要回收,最後纔是用什麼方式回收。併發

Java的內存分配原理與C/C++不一樣,C/C++每次申請內存時都要malloc進行系統調用,而系統調用發生在內核空間,每次都要中斷進行切換,這須要必定的開銷,而Java虛擬機是先一次性分配一塊較大的空間,而後每次new時都在該空間上進行分配和釋放,減小了系統調用的次數,節省了必定的開銷,這有點相似於內存池的概念;二是有了這塊空間事後,如何進行分配和回收就跟GC機制有關了。jvm

三. 垃圾檢測(標記)算法

什麼樣的對象纔是垃圾?

若是一個對象沒有被其餘對象所引用該對象就是無用的,此對象就被稱爲垃圾,其佔用的內存也就要被銷燬。那麼天然而然的就引出了咱們的第二個問題,判斷對象爲垃圾的算法都有哪些?ide

標記垃圾的算法

Java中標記垃圾的算法主要有兩種, 引用計數法可達性分析算法。咱們首先來介紹引用計數法。spa

  • 引用計數法

  引用計數法就是給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任什麼時候候計數器爲 0 的對象就是不可能再被使用的,能夠當作垃圾收集。這種方法實現起來很簡單並且優缺點都很明顯。線程

  

    •   優勢 執行效率高,程序執行受影響較小3d

    •   缺點 沒法檢測出循環引用的狀況,致使內存泄露

  什麼是循環引用呢?

  假設有A和B兩個對象之間互相引用,也就是說A對象中的一個屬性是B,B中的一個屬性時A,這種狀況下因爲他們的相互引用

  咱們舉一個簡單的例子。

public class MyObject {
    public MyObject childNode;
}

public class ReferenceCounterProblem {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
        object1.childNode = object2;
        object2.childNode = object1;
    }
}
  • 可達性分析

可達性分析基本思路是把全部引用的對象想象成一棵樹,從樹的根結點 GC Roots 出發,持續遍歷找出全部鏈接的樹枝對象,這些對象則被稱爲「可達」對象,或稱「存活」對象。不能到達的則被可回收對象。

下面這張圖就是可達性分析的描述:

 

 

 咱們發現,GC Roots 自己是一個出發點,也就是說咱們每次進行可達性分析的時候都要從這個初始點出發。換句話說,初始點咱們必定是可達的。那麼,Java 裏有哪些對象能夠做爲GC Roots呢?主要有如下四種:

  • 虛擬機棧(幀棧中的本地變量表)中引用的對象。
  • 方法區中靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中 JNI 引用的對象。

總之,JVM在作垃圾回收的時候,會檢查堆中的全部對象是否會被這些根集對象引用,不可以被引用的對象就會被垃圾收集器回收。

四. 垃圾收集(回收)算法

已經可以肯定那些對象能夠被視爲垃圾了。下面咱們能夠分析一下,如何去回收這些垃圾,一樣的,有一系列算法。首先咱們定義一個規則肯定那些是垃圾、存活對象、空白空間

 

 

 

垃圾收集算法有四種:

  • 標記-清除算法
  • 標記-整理算法
  • 複製算法
  • 分代收集算法

一、標記-清理

第一步(標記),利用可達性遍歷內存,把「存活」對象和「垃圾」對象進行標記。第二步(清理),咱們再遍歷一遍,把全部「垃圾」對象所佔的空間直接 清空 便可。

結果以下:

 

 

 

特色:

  • 簡單方便
  • 容易產生內存碎片

二、標記-整理

上面的方法咱們發現會產生內存碎片,所以在這個方法中一樣爲兩步:

第一步(標記):利用可達性遍歷內存,把「存活」對象和「垃圾」對象進行標記。

第二步(整理):把全部存活對象堆到同一個地方,這樣就沒有內存碎片了。

結果以下:

 

 

 

特色:

  • 適合存活對象多,垃圾少的狀況
  • 須要整理的過程

三、複製

將內存按照容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊用完了,就將還活着的對象複製到另外一塊上,而後再把使用過的內存空間一次性清理掉

過程以下:

 

 

 

特色:

  • 簡單
  • 不會產生碎片
  • 內存利用率過低,只用了一半

四、分代收集算法

java中的垃圾回收大體在兩部分,第一個就是堆、第二個就是方法區。爲此先看方法區是如何進行垃圾回收的。

一、方法區的垃圾回收

方法區又叫作永久代。永久代的垃圾回收主要有兩部分:廢棄常量和無用的類。

  • 廢棄常量

    第一步:斷定一個常量是不是廢棄常量:沒有任何一個地方對這個常量進行引用就表示是廢棄常量。

    第二步:垃圾回收

  • 無用的類

    第一步:斷定一個類是不是「無用的類」:須要知足下面三個條件:

      • Java堆中不存在該類的任何實例,也就是該類的全部實例都被回收
      • 加載該類的ClassLoader已經被回收
      • 該類對應的Class對象在任何地方沒有引用了,也不能經過反射訪問該類的方法。

    第二步:知足上面三個條件就能夠回收了,但不是強制的。

注意:《java虛擬機規範》裏面曾經說到過,不要求虛擬機對方法區進行垃圾回收。並且方法區進行垃圾回收性價比比較低

二、Java 堆的垃圾回收:

先來看一下 Java 堆的結構。

 

 

 Java 堆空間分紅了三部分,這三部分用來存儲三類數據:

  • 剛剛建立的對象。
  • 存活了一段時間的對象。
  • 永久存在的對象。

也就是說,常規的 Java 堆至少包括了 新生代 和 老年代 兩塊內存區域,並且這兩塊區域有很明顯的特徵:

  • 新生代:存活對象少、垃圾多
  • 老年代:存活對象多、垃圾少

針對這種特色,咱們有如下兩種方案;

(1)新生代-複製 回收機制

對於新生代區域,因爲每次 GC 都會有大量新對象死去,只有少許存活。所以採用 複製 回收算法,GC 時把少許的存活對象複製過去便可。可是從上面咱們能夠看到,新生代也劃分了三個部分比例:Eden:S1:S2=8:1:1。

其中 Eden 意爲伊甸園,形容有不少新生對象在裏面建立;S1和S2中的S表示Survivor,爲倖存者,即經歷 GC 後仍然存活下來的對象。

工做原理以下:

  1. 首先,Eden對外提供堆內存。當 Eden區快要滿了,觸發垃圾回收機制,把存活對象放入 Survivor A 區,清空 Eden 區;

  2. Eden區被清空後,繼續對外提供堆內存;

  3. 當 Eden 區再次被填滿,對 Eden區和 Survivor A 區同時進行垃圾回收,把存活對象放入 Survivor B區,同時清空 Eden 區和Survivor A 區;

  4. 當某個 Survivor區被填滿,把多餘對象放到Old 區;

  5. 當 Old 區也被填滿時,進行 下一階段的垃圾回收。

(2)老年代-標記整理 回收機制

老年代的特色是:存活對象多、垃圾少。所以,根據老年代的特色,這裏僅僅經過少許地移動對象就能清理垃圾,並且不存在內存碎片化。也就是標記整理的回收機制。既然是標記整理算法,並且老年代內部也不存在着內存劃分,因此只須要根據標記整理的具體步驟進行垃圾回收就行了。

五. 垃圾回收器

若是說收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。

在瞭解 垃圾回收器以前,首先得了解一下垃圾回收器的幾個名詞。

1. 吞吐量

CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比值。好比說虛擬機總運行了 100 分鐘,用戶代碼時間 99 分鐘,垃圾回收 時間 1 分鐘,那麼吞吐量就是 99%。

2. 停頓時間

停頓時間 指垃圾回收器正在運行時,應用程序 的 暫停時間。

3. GC的名詞

新生代GC:Minor GC

老年代GC:Major GC

4. 併發與並行

(1)串行(Parallel)

垃圾回收線程 進行垃圾回收工做,但此時 用戶線程 仍然處於 等待狀態。

(2)併發(Concurrent)

這裏的併發指 用戶線程 與 垃圾回收線程 交替執行。

(3)並行(Parallel)

這裏的並行指 用戶線程 和多條 垃圾回收線程 分別在不一樣 CPU 上同時工做。

下面其中垃圾回收器是基於HotSpot虛擬機。先給一張圖看一下

 

 在 JVM 中,具體實現有 Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old、G1 等。在上圖中,你能夠看到 不一樣垃圾回收器 適合於 不一樣的內存區域,若是兩個垃圾回收器之間 存在連線,那麼表示二者能夠 配合使用。

下面對這其中垃圾回收器有一個瞭解。

第一種:Serial(單線程)

Serial 回收器是最基本的 新生代垃圾回收器,是單線程的垃圾回收器。採用的是 複製算法。垃圾清理時,Serial回收器不存在線程間的切換,所以,在單 CPU` 的環境下,垃圾清除效率比較高。

第二種:Serial Old(單線程)

Serial Old回收器是 Serial回收器的老生代版本,單線程回收器,使用 標記-整理算法。在 JDK1.5 及其之前,它常與Parallel Scavenge回收器配合使用,達到較好的吞吐量,另外它也是 CMS 回收器在Concurrent Mode Failure時的後備方案。

第三種:ParNew(多線程)

ParNew回收器是在Serial回收器的基礎上演化而來的,屬於Serial回收器的多線程版本,採用複製算法。運行在新生代區域。在實現上,二者共用不少代碼。在不一樣運行環境下,根據CPU核數,開啓不一樣的線程數,從而達到最優的垃圾回收效果。

 

 

第四種:Parallel Scavenge(多線程)

Parallel Scavenge回收器也是運行在新生代區域,屬於多線程的回收器,採用複製算法。與ParNew不一樣的是,ParNew回收器是經過控制垃圾回收的線程數來進行參數調整,而Parallel Scavenge回收器更關心的是程序運行的吞吐量。即一段時間內用戶代碼運行時間佔總運行時間的百分比。

第五種:Parallel Old(多線程)

Parallel Old回收器是Parallel Scavenge回收器的老生代版本,屬於多線程回收器,採用標記-整理算法。Parallel Old回收器和Parallel Scavenge回收器一樣考慮了吞吐量優先這一指標,很是適合那些注重吞吐量和CPU資源敏感的場合。

 

 

第六種:CMS(多線程回收)

CMS回收器是在最短回收停頓時間爲前提的回收器,屬於多線程回收器,採用標記-清除算法。

 

 

第七種:G1回收器

G1是 JDK 1.7中正式投入使用的用於取代CMS的壓縮回收器。它雖然沒有在物理上隔斷新生代與老生代,可是仍然屬於分代垃圾回收器。G1仍然會區分年輕代與老年代,年輕代依然分有Eden區與Survivor區。

G1首先將堆分爲大小相等的 Region,避免全區域的垃圾回收。G1的分區示例以下圖所示:

 

 

這種使用區域劃份內存空間以及有優先級的區域回收方式,保證G1回收器在有限的時間內能夠得到儘量高的回收效率。

下面對這幾種垃圾回收機制進行一個總結:

 

 

參考:https://baijiahao.baidu.com/s?id=1636852721632353675&wfr=spider&for=pc

相關文章
相關標籤/搜索