天天看點

Java GC垃圾回收機制一、Java GC二、GC 回收的對象三、GC 回收過程

文章目錄

  • 一、Java GC
  • 二、GC 回收的對象
  • 三、GC 回收過程
    • 1、圖解分代記憶體
      • 1.1 年輕代
      • 1.2 年老代
      • 1.3 永久代
    • 2、圖解 GC 回收過程
      • 對象配置設定政策
    • 3、Minor GC 和 Full GC

本文内容基于目前使用最廣泛的 HotSpot JVM。

參考書籍:《深入了解Java虛拟機:JVM進階特性與最佳實踐》–周志明

相關文章:

JVM記憶體模型

一、Java GC

Java 垃圾回收機制是由 GC(Garbage Collection)垃圾收集器實作的,它是 JVM 提供的一種自動記憶體管理和垃圾清掃機制,在空閑時間、不定時回收、無任何對象引用的對象所占據的記憶體空間,進而不容易出現記憶體洩露和記憶體溢出問題,一般簡稱「Java GC」或「JVM GC」。

  • 垃圾:無任何對象引用的對象;
  • 回收:清理 ‘垃圾’ 占用的記憶體空間,而非對象本身;
  • 發生時間:在程式空閑時不定時回收;
  • 發生地點:一般發生在堆記憶體中;
  • 一般不需要、也不推薦程式員手動編寫記憶體回收和垃圾清理代碼。

Java GC 主要負責三件事:

  • What:确定哪些記憶體需要 GC 回收?
  • When:确定什麼時候需要 GC 回收?
  • How:如何執行 GC 回收?

二、GC 回收的對象

Java 中那些「不可達的對象」就會變成垃圾,等待被 GC 回收。

"不可達":不能通過任何途徑引用該對象。

Java 中定義了四種引用:

  • 強引用:Strong Reference,代碼中最普遍的形式,GC 永遠不會回收掉這種引用對象;
  • 軟引用:Soft Reference,指一些還有用但并非必需的對象,在系統将要發生記憶體溢出之前,會對這類對象進行回收;
  • 弱引用:Weak Reference,指非必需對象,比軟引用的強度更弱一些。當 GC 工作時,無論目前記憶體是否足夠都會回收掉隻被弱引用關聯的對象;
  • 虛引用:是最弱的一種引用關系,一個對象是否有虛引用的存在完全不影響其生存空間。

在 JVM 的記憶體空間中,程式計數器、虛拟機棧和本地方法棧三個區域都是線程私有的,随線程一起生或死,在方法結束或線程結束時,占用的記憶體空間自然就回收了,不需要過多考慮回收的問題。

Java 堆記憶體和方法區: 這兩個區域是線程共享的,記憶體的配置設定和回收都是動态的,沒有确定性,是以 GC 回收重點關注的就是這兩部分記憶體。

三、GC 回收過程

目前商業虛拟機的垃圾收集基本都采用「分代收集 - Generational Collection」算法,這種算法根據對象的存活周期不同将記憶體劃分為不同的區域,然後根據各個區域的特點采用最适當的收集算法中,進而提高回收效率。

1、圖解分代記憶體

在 HotSpot JVM 中,需要進行 GC 回收的記憶體空間主要是 Java 堆記憶體和方法區,是以為了更快、更好的回收記憶體,對這兩個區域進行分代劃分。

其中,Java 堆是垃圾收集器管理的重點區域,是以也被稱為

GC 堆(Garbage Collected Heap)

,并對堆記憶體進一步分代劃分為 「年輕代和年老代」(也叫新生代和老年代)。

GC 回收記憶體的分代示意圖如下所示:

Java GC垃圾回收機制一、Java GC二、GC 回收的對象三、GC 回收過程

也可以簡化成下面這樣:

Java GC垃圾回收機制一、Java GC二、GC 回收的對象三、GC 回收過程

我再把它簡化一下,并加上中文說明:

Java GC垃圾回收機制一、Java GC二、GC 回收的對象三、GC 回收過程

接下來結合上面的圖,分别對三個 Generation 進行文字解析。

1.1 年輕代

  • 堆記憶體被劃分為 年輕代 和 年老代,其中年輕代又分為 Eden、S0、S1,HotSpot JVM 預設的空間比例為:

    Eden:S0:S1 = 8:1:1

  • 幾乎所有新生成的對象首先配置設定在年輕代的 Eden 區,當 Eden 區滿了之後,會把還存活的對象依次轉移到 S0、S1;
  • 年輕代對象 98% 是朝生夕死的,剩下小部分壽命長的對象會進入年老代;
  • GC 回收年輕代時,效率很高,正常應用進行一次垃圾收集一般可以回收 70%~95% 的空間。

1.2 年老代

  • 年老代對象用于存儲長期存活的對象和一些大對象(如大數組、大字元串,需要大量連續存儲空間象);
  • 當年輕代空間滿了,或者在年輕代中經曆了 N 次垃圾回收後仍然存活的對象,都會被放到年老代中;
  • 堆記憶體可通過

    -Xms -Xmx

    參數調整大小,年輕代可通過

    -Xmn

    調整大小,

    年老代的記憶體大小 = 堆記憶體 - 年輕代

1.3 永久代

在 HotSpot JVM 中,習慣把方法區稱為「永久代」,本質上兩者并不等價,隻是因為在 GC 分代收集中使用永久代來實作方法區而已。

  • 方法區主要存儲類資訊、常量、靜态變量等資料;
  • 該區域可通過

    -XX:PermSize -XX:MaxPermSize

    調整記憶體大小;
  • 該區域的回收目标主要是廢棄常量和無用的類,但是回收效率和成本效益都很低,GC 很少在該區域執行;
  • JDK 1.8 對記憶體結構進行了優化,将方法區從永久代抽取出來,去掉了永久代空間,取而代之的是元空間(MetaSpace,Native Memory),是以也不會再出現

    java.lang.OutOfMemoryError: PermGen error

    錯誤。

2、圖解 GC 回收過程

在上面分代記憶體的示意圖基礎上,再結合 GC 回收記憶體的過程,畫出 GC 回收過程的簡圖:

Java GC垃圾回收機制一、Java GC二、GC 回收的對象三、GC 回收過程

對 GC 回收過程具體說明如下:

  • 幾乎所有新生成的對象首先存放在年輕代的 Eden 區,大部分對象朝生夕死;
  • 當新對象生成時,在 Eden 申請空間失敗(因為空間不足等),會觸發一次 GC (Minor GC),先将 Eden 中存活對象複制到 Survivor0 區(簡稱 S0),然後清空 Eden;
  • 當 S0 也存放滿了時,觸發 Minor GC ,将 Eden、S0 存活對象複制到另一個 Survivor1 區(簡稱 S1),然後清空 Eden、S0,最後交換 S0 和 S1,即保持 S1 為空, 如此往複;
  • 當 S1 不足以存放 Eden 和 S0 的存活對象時,會将存活對象直接存放到老年代;
  • 同時,當對象在 Survivor 區躲過一次 GC 的話,會将其對象年齡加 1,預設情況下對象年齡達到 15 歲時,也會移動到老年代中;
  • 當老年代也滿了,就會觸發一次 Full GC,也就是新生代、老年代都進行回收。

對象配置設定政策

  • 大部分對象首先配置設定在年輕代的 Eden 區;
  • 在年輕代中長期存活的對象會在 Minor GC 過程中進入老年代;
  • 大對象(如大數組、大字元串)直接配置設定在老年代;

3、Minor GC 和 Full GC

Minor GC:

  • 也稱年輕代 GC、新生代 GC,指發生在年輕代的垃圾收集動作;
  • 因為 Java 對象大多具備「朝生夕滅」的特性,是以 Minor GC 非常頻繁,一般回收速度也比較快。

Full GC:

  • 也稱為

    Major GC

    、老年代 GC,指發生在老年代的垃圾收集動作;
  • 執行 Full GC 時,經常會伴随至少一次的 Minor GC;
  • Full GC 的速度一般會比 Minor GC 慢 10 倍以上,是以要盡量避免出現頻繁的 Full GC 動作。