深刻淺出Java垃圾回收機制

簡述

虛擬機中的共劃分爲三個代:年輕代(Young Generation)、老年代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。算法

jvm內存參數

-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M -vmargs 說明後面是VM的參數,因此後面的其實都是JVM的參數了 -Xms128m JVM初始分配的堆內存 -Xmx512m JVM最大容許分配的堆內存,按需分配 -XX:PermSize=64M JVM初始分配的非堆內存 -XX:MaxPermSize=128M JVM最大容許分配的非堆內存,按需分配安全

按代的垃圾回收機制

在Java中,開發人員沒法直接在程序代碼中清理內存,而是由垃圾回收器自動尋找沒必要要的垃圾對象,而且清理掉他們。垃圾回收器會在下面兩種假設(hypotheses)成立的狀況下被建立(稱之爲假設不如改成推測(suppositions)或者前提(preconditions))。多線程

  • 大多數對象會很快變得不可達。
  • 只有不多的由老對象(建立時間較長的對象)指向新生對象的引用。

這些假設咱們稱之爲弱年代假設( weak generational hypothesis)。爲了強化這一假設,HotSpot虛擬機將其物理上劃分爲兩個–新生代(young generation)和老年代(old generation)。jvm

  • 新生代(Young generation): 絕大多數最新被建立的對象會被分配到這裏,因爲大部分對象在建立後會很快變得不可到達,因此不少對象被建立在新生代,而後消失。對象從這個區域消失的過程咱們稱之爲」minor GC「。性能

  • 老年代(Old generation): 對象沒有變得不可達,而且重新生代中存活下來,會被拷貝到這裏。其所佔用的空間要比新生代多。也正因爲其相對較大的空間,發生在老年代上的GC要比新生代少得多。對象從老年代中消失的過程,咱們稱之爲」major GC「(或者」full GC「)線程

請看下面這個圖表。對象

 圖1 : GC 空間 & 數據流

上圖中的持久代( permanent generation )也被稱爲方法區(method area)。他用來保存類常量以及字符串常量。所以,這個區域不是用來永久的存儲那些從老年代存活下來的對象。這個區域也可能發生GC。而且發生在這個區域上的GC事件也會被算爲major GC。事件

若是老年代的對象須要引用一個新生代的對象,會發生什麼呢?

爲了解決這個問題,老年代中存在一個」card table「,他是一個512 byte大小的塊。全部老年代的對象指向新生代對象的引用都會被記錄在這個表中。當針對新生代執行GC的時候,只須要查詢card table來決定是否能夠被收集,而不用查詢整個老年代。這個card table由一個write barrier來管理。write barrier給GC帶來了很大的性能提高,雖然由此可能帶來一些開銷,但GC的總體時間被顯著的減小。圖片

輸入圖片說明

新生代的空間

新生代是用來保存那些第一次被建立的對象,他能夠被分爲三個空間:內存

  • 一個伊甸園空間(Eden )
  • 兩個倖存者空間(Survivor )

一共有三個空間,其中包含兩個倖存者空間。每一個空間的執行順序以下:

  1. 絕大多數剛剛被建立的對象會存放在伊甸園空間。
  2. 在伊甸園空間執行了第一次GC以後,存活的對象被移動到其中一個倖存者空間。
  3. 此後,在伊甸園空間執行GC以後,存活的對象會被堆積在同一個倖存者空間。
  4. 當一個倖存者空間飽和,還在存活的對象會被移動到另外一個倖存者空間。以後會清空已經飽和的那個倖存者空間。
  5. 在以上的步驟中重複幾回依然存活的對象,就會被移動到老年代。

若是你仔細觀察這些步驟就會發現,其中一個倖存者空間必須保持是空的。若是兩個倖存者空間都有數據,或者兩個空間都是空的,那必定標誌着你的系統出現了某種錯誤。 經過頻繁的minor GC將數據移動到老年代的過程能夠用下圖來描述:

輸入圖片說明

須要注意的是HotSpot虛擬機使用了兩種技術來加快內存分配。他們分別是是」bump-the-pointer「和「TLABs(Thread-Local Allocation Buffers)」。

  • Bump-the-pointer技術跟蹤在伊甸園空間建立的最後一個對象。這個對象會被放在伊甸園空間的頂部。若是以後再須要建立對象,只須要檢查伊甸園空間是否有足夠的剩餘空間。若是有足夠的空間,對象就會被建立在伊甸園空間,而且被放置在頂部。這樣以來,每次建立新的對象時,只須要檢查最後被建立的對象。這將極大地加快內存分配速度。可是,若是咱們在多線程的狀況下,事情將大相徑庭。若是想要以線程安全的方式以多線程在伊甸園空間存儲對象,不可避免的須要加鎖,而這將極大地的影響性能。
  • TLABs 是HotSpot虛擬機針對這一問題的解決方案。該方案爲每個線程在伊甸園空間分配一塊獨享的空間,這樣每一個線程只訪問他們本身的TLAB空間,再與bump-the-pointer技術結合能夠在不加鎖的狀況下分配內存。

老年代GC處理機制

JDK7一共有5種GC類型:

  • Serial GC
  • Parallel GC
  • Parallel Old GC (Parallel Compacting GC)
  • Concurrent Mark & Sweep GC (or 「CMS」)
  • Garbage First (G1) GC

1. Serial GC (-XX:+UseSerialGC)

新生代空間的GC方式咱們在前面已經介紹過了,在老年代空間中的GC採起稱之爲」mark-sweep-compact「的算法。

  1. 算法的第一步是標記老年代中依然存活對象。(標記)
  2. 第二步,從頭開始檢查堆內存空間,而且只留下依然倖存的對象。(清理)

最後一步,從頭開始,順序地填滿堆內存空間,而且將對內存空間分紅兩部分:一個保存着對象,另外一個空着(壓縮)。

2. Parallel GC (-XX:+UseParallelGC)

輸入圖片說明

從上圖中,你能夠輕易地看出serial GC和parallel GC的區別,serial GC只使用一個線程執行GC,而parallel GC使用多個線程,所以parallel GC更高效。這種GC在內存充足以及多核的狀況下會頗有用,所以咱們也稱之爲」throughput GC「。

3. Parallel Old GC(-XX:+UseParallelOldGC)

Parallel Old GC在JDK5以後出現。與parallel GC相比,惟一的區別在於針對老年代的GC算法。Parallel Old GC分爲三步:標記-彙總-壓縮(mark – summary – compaction)。彙總(summary)步驟與清理(sweep)的不一樣之處在於,其將依然倖存的對象分發到GC預先處理好的不一樣區域,算法相對清理來講略微複雜一點。 ###4. CMS GC (-XX:+UseConcMarkSweepGC) 輸入圖片說明

第一步初始化標記(initial mark) 比較簡單。這一步驟只是查找那些距離類加載器最近的倖存對象。所以,停頓的時間很是短暫。在以後的並行標記( concurrent mark )步驟,全部被倖存對象引用的對象會被確認是否已經被追蹤和校驗。這一步的不一樣之處在於,在標記的過程當中,其餘的線程依然在執行。在從新標記(remark)步驟,會再次檢查那些在並行標記步驟中增長或者刪除的與倖存對象引用的對象。最後,在並行交換( concurrent sweep )步驟,轉交垃圾回收過程處理。垃圾回收工做會在其餘線程的執行過程當中展開。一旦採起了這種GC類型,由GC致使的暫停時間會極其短暫。CMS GC也被稱爲低延遲GC。它常常被用在那些對於響應時間要求十分苛刻的應用之上。

固然,這種GC類型在擁有stop-the-world時間很短的優勢的同時,也有以下缺點:

  • 它會比其餘GC類型佔用更多的內存和CPU
  • 默認狀況下不支持壓縮步驟 在使用這個GC類型以前你須要慎重考慮。若是由於內存碎片過多而致使壓縮任務不得不執行,那麼stop-the-world的時間要比其餘任何GC類型都長,你須要考慮壓縮任務的發生頻率以及執行時間。

5. G1 GC 一種全新的內存模型

G1算法將堆劃分爲若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停全部應用線程的方式,將存活對象拷貝到老年代或者Survivor空間。老年代也分紅不少區域,G1收集器經過將對象從一個區域複製到另一個區域,完成了清理工做。這就意味着,在正常的處理過程當中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內存碎片問題的存在了。

相關文章
相關標籤/搜索