天天看點

閑談JVM(六):JVM垃圾回收概述前言JVM垃圾回收機制結語

文章目錄

  • 前言
  • JVM垃圾回收機制
      • 何為垃圾
    • 為何分代管理
    • 生代記憶體配置設定
    • 吞吐量 VS 暫停時間
      • 吞吐量(throughput)
      • 暫停時間(pause times)
    • 垃圾收集器種類
      • 新生代收集器
      • 老生代收集器
    • Partial GC VS Full GC
  • 結語

前言

深入了解JVM虛拟機:(二)垃圾收集器概述

閑談JVM(三):淺析本地元空間參數配置

閑談JVM(二):淺析新老生代參數配置

閑談JVM(一):淺析JVM Heap參數配置

本篇,我們聊聊JVM的垃圾回收機制。

JVM垃圾回收機制

衆所周知,Java語言的核心特性之一,就是JVM的垃圾回收(GC)機制,可以讓開發者無需關注程式的記憶體配置設定問題,将這部分瑣碎且容易出錯的操作交給JVM來進行處理。

但是這并不代表着作為開發者不需要去了解JVM的垃圾收集機制,當垃圾收集是主要瓶頸時,了解此垃圾收集實作的某些方面會非常有用。

何為垃圾

那在JVM中,哪些對象可以被标記為“垃圾”?

官方文檔對此的定義是:

An object is considered garbage when it can no longer be reached from any pointer in the running program.

在程式運作過程中,沒有任何指針指向的對象,可以被認為是“垃圾對象”。

最簡單的垃圾回收算法會周遊每個可通路的對象,剩下的任何對象都被視為垃圾。

但是這種方法花費的時間與活動對象的數量成正比,這對于維護大量活動資料的大型應用程式是不可行的。

Java虛拟機結合了許多不同的垃圾收集算法,這些算法使用分代收集進行組合。

為何分代管理

閑談JVM(六):JVM垃圾回收概述前言JVM垃圾回收機制結語

在Java虛拟機中,對象的生命周期大緻可以如上圖所示,x軸是對象壽命,以配置設定的位元組為機關。y軸上的位元組數是具有相應生存期的對象中的總位元組數。

絕大多數的對象的生命周期是非常短暫的,隻有少部分的對象生命周期非常的長,通常有一些在初始化時配置設定的對象,這些對象一直存在,直到程序退出。

由此,為了針對這種情況進行優化,Java虛拟機采用了分代管理的政策,即将整個堆區切分成幾個存儲着不同年齡對象的記憶體池,将其分為了新生代、老生代、本地元空間(永生代/方法區)。

閑談JVM(六):JVM垃圾回收概述前言JVM垃圾回收機制結語

當某個分代記憶體不足時,垃圾回收會在該分代中進行,而會影響其他分代。

絕大多數對象配置設定在新生代中,并且大多數對象在那裡死亡。是以,新生代的垃圾回收會頻繁進行,經過多次垃圾回收仍存活的對象,可以進行“晉升”,進入到老年代當中。當然也有一部分大對象,其大小超過指定的門檻值時,會直接被配置設定在老生代中,當老生代記憶體不足時,會進行老生代的垃圾回收,周而複始,保持整個堆區的記憶體可用性。

生代記憶體配置設定

對于新老生代的記憶體大小,我們可以通過參數進行配置,但是這部分實體記憶體真正全部配置設定給JVM了麼?

我們來看一下官方文檔的解釋:

At initialization, a maximum address space is virtually reserved but not allocated to physical memory unless it is needed. The complete address space reserved for object memory can be divided into the young and tenured generations.

在JVM初始化階段時,最大的位址空間實際上是保留的,除非需要,否則不會配置設定給實體記憶體。

這部分記憶體空間會被保留,等待配置設定至新老生代。

也就是說,當我們使用-Xmn128m 參數指定了新生代記憶體大小,但JVM初始化階段,并未真的使用了OS這麼大的記憶體,而是預先占用,在真正使用的時候,才進行配置設定。

閑談JVM(六):JVM垃圾回收概述前言JVM垃圾回收機制結語

對于新生代記憶體,JVM又将其分為三部分,eden、survivor01、survivor02,大多數對象初始化階段會被配置設定在eden區域,而兩個survivor區域的其中一個會一直保持為空,用于GC收集階段的對象整理。

劃分出新生代的另一個好處是某種程度上解決了碎片化問題,或者說将最壞的情況推遲了。

那些存活時間短的小對象本來可能産生碎片化問題,但都在新生代的垃圾收集中被清理了。

由于存活時間長的對象被移到老年代時被更緊湊的配置設定空間,老年代也更加緊湊了。

随着時間推移(如果你的應用運作時間足夠長),老年代也會産生碎片化,這時需要運作一次或是幾次完全垃圾收集,同時JVM也有可能抛出記憶體溢出錯誤。

但是劃分出新生代推遲了出現最壞情況的時間,這對于很多應用程式來說已經足夠了。對于多數應用程式而言,它的确降低了stop-the-world垃圾收集的頻率和記憶體溢出錯誤的機會。

由此可以看出堆區記憶體分代管理的好處,可以将各生代的記憶體根據實際情況更加合理的利用與調配。

吞吐量 VS 暫停時間

對于大多數的應用領域,評估一個垃圾收集(GC)算法如何根據如下兩個标準:

  • 吞吐量越高算法越好
  • 暫停時間越短算法越好

首先讓我們來明确垃圾收集(GC)中的兩個術語:吞吐量(throughput)和暫停時間(pause times)。

吞吐量(throughput)

JVM在專門的線程(GC threads)中執行GC。隻要GC線程是活動的,它們将與應用程式線程(application threads)争用目前可用CPU的時鐘周期。

簡單點來說,吞吐量是指應用程式線程用時占程式總用時的比例。例如,吞吐量99/100意味着100秒的程式執行時間應用程式線程運作了99秒, 而在這一時間段内GC線程隻運作了1秒。

暫停時間(pause times)

暫停時間是指一個時間段内應用程式線程讓與GC線程執行而完全暫停。 例如,GC期間100毫秒的暫停時間意味着在這100毫秒期間内沒有應用程式線程是活動的。

如果說一個正在運作的應用程式有100毫秒的“平均暫停時間”,那麼就是說該應用程式所有的暫停時間平均長度為100毫秒。同樣,100毫秒的“最大暫停時間”是指該應用程式所有的暫停時間最大不超過100毫秒。

高吞吐量最好是指這會讓應用程式的最終使用者感覺隻有應用程式線程在做“生産性”工作。

直覺上,吞吐量越高程式運作越快。 低暫停時間最好因為從最終使用者的角度來看不管是GC還是其他原因導緻一個應用被挂起始終是不好的。 這取決于應用程式的類型,有時候甚至短暫的200毫秒暫停都可能打斷終端使用者體驗。

是以,具有低的最大暫停時間是非常重要的,特别是對于一個互動式應用程式。

垃圾收集器種類

在JVM中,由于分代管理的存在,垃圾收集器可以分為兩大類,新生代收集器與老生代收集器,下面我們來進行簡單的介紹。

新生代收集器

新生代的垃圾收集器,主要有三種,分别如下:

1、Serial 收集器,它是一個單線程的收集器,但它的單線程的意義并不僅僅說明它隻會是使用一個 CPU 或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。

2、ParNew 收集器,ParNew 收集器其實就是 Serial 收集器的多線程版本。ParNew 最重要的一點,是唯一的可以與老生代的CMS 收集器配合使用的新生代收集器,可以通過參數-XX:+UseParNewGC進行指定。

3、Parallel Scavenge 收集器,它與其他收集器的不同之處在于:它的關注點與其他收集器不同。CMS 等收集器的關注點是盡可能地縮短垃圾收集時使用者線程的停頓時間,而 Parallel Scavenge 收集器的目标則是達到一個可控制的吞吐量( Throughput)。

老生代收集器

老生代的垃圾收集器,主要分為四種,分别如下:

1、Serial Old 收集器,Serial Old 是 Serial 收集器的老年代版本,它同樣是一個單線程收集器,使用 “标記-整理” 算法。一般情況下,不會使用。

2、Parallel old 收集器,Parallel Scavenge 收集器的老年代版本,使用多線程和 “标記-整理” 算法。

3、CMS 收集器,以擷取最短回收停頓時間為目标,目前較為推薦的GC 收集器,多數應用于網際網路站或者B/S系統的伺服器端上。

4、G1 收集器,Java 9以後的預設收集器,目前最炙手可熱的GC 收集器,可以說兼顧了性能與時間的GC收集器。

在Java8中,預設的GC收集器采用了Parallel GC,也可以通過參數

-XX:+UseParallelGC

進行指定,來看一下Oracle官方的說法:

The parallel collector (also referred to here as the throughput collector) is a generational collector similar to the serial collector; the primary difference is that multiple threads are used to speed up garbage collection. The parallel collector is enabled with the command-line option

-XX:+UseParallelGC

. By default, with this option, both minor and major collections are executed in parallel to further reduce garbage collection overhead.

The parallel collector is selected by default on server-class machines. In addition, the parallel collector uses a method of automatic tuning that allows you to specify specific behaviors instead of generation sizes and other low-level tuning details. You can specify maximum garbage collection pause time, throughput, and footprint (heap size).

Partial GC VS Full GC

前面的介紹中,我們提到了新生代的GC與老生代的GC,但事實上,針對HotSpot VM的實作,它裡面的GC其實準确分類隻有兩大種:

Partial GC:并不收集整個GC堆的模式。

  • Young GC:隻收集新生代的GC
  • Old GC:隻收集老生代的GC。隻有CMS的concurrent collection是這個模式
  • Mixed GC:收集整個新生代以及部分老生代的GC。隻有G1有這個模式

Full GC:收集整個堆,包括新生代、老生代、永生代(本地元空間)等所有部分的模式。

關于新老生代的GC觸發時機,我們前面已經提及,當所屬生代的空間不足時,會觸發所在生代的GC操作,

但Full GC的觸發時機則有所不同,其可能的觸發時機如下:

1、當需要在永生代(本地元空間)配置設定空間但已經沒有足夠空間時;

2、在代碼中執行System.gc(),或者執行JVM指令

jmap -histo:live <pid>

3、當準備要觸發一次Young GC時,如果發現統計資料說之前新生代的平均晉升大小比目前老生代剩餘的空間大,則不會觸發Young GC而是轉為觸發Full GC(因為HotSpot VM的GC裡,除了CMS的concurrent collection之外,其它能收集老生代的GC都會同時收集整個GC堆,包括新生代,是以不需要事先觸發一次單獨的young GC)

Full GC的執行代價會非常的高,它會對整個堆區進行整理收集,并進行Stop-the-world,暫停全部使用者線程,直到Full GC執行結束。

結語

本篇我們對JVM的垃圾回收機制進行了簡單的概述,在下一篇中,将會對具體的垃圾回收器進行詳細的分析,敬請期待。