天天看點

JVM:Garbage Collection GC

JVM:Garbage Collection GC

不管去哪裡,都要記得這個圖咯。今天重點看看GC垃圾回收是怎麼搞的😄😄😄

由于回收的主要區域是堆記憶體,我們需要重新看看堆記憶體到底是怎麼樣子的

堆記憶體🌲

JVM:Garbage Collection GC
  • Young Generation Survivor Space:新生代,包括了Eden Space ,S0,S1區域

    💰Eden Space:最開始的對象都配置設定在這裡,靜靜等待着垃圾回收

    💰S0:即Survivor0 space,當Eden Space垃圾回收後存活的對象放到這裡來

    💰S1:當S0區域的對象逃過了GC的魔掌就會移到S1區域

  • Old Generation tenured:老年代,或者舊生代,主要是Young Generation的對象還沒有被回收就會移到這個老年代來了。
  • Permanent Generation:俗稱永久代,JDK1.8裡面已經把它從堆區域移除了,成為了方法區,用來存儲常量,類資訊等等

垃圾回收流程📖

JVM:Garbage Collection GC

這個就和上面介紹的差不多,總之就是Eden->S0->S1->Tenured

說的那麼多,好像說的那麼爽,結果沒有一個點能夠卡得上,到底什麼是垃圾啊

什麼是垃圾👀

主要有兩種方式來判别是不是垃圾:引用計數法和可達性分析。java采用了可達性分析法

引用計數法

引用計數法:引用計數算法是垃圾回收器中的早起政策,在這種方法中,堆中的每個對象執行個體都有一個引用計數器,點一個對象被建立時,且該對象執行個體配置設定給一個變量,該變量計數設定為1 ,當任何其他變量指派為這個對象的引用時,計數加1 ,(a=b ,則b引用的對象執行個體計數器+1)但當一個對象執行個體的某個引用超過了生命周期或者被設定為一個新值時,對象執行個體的引用計數器減1,任何引用計數器為0 的對象執行個體可以當做垃圾收集。 當一個對象的執行個體被垃圾收集是,它引用的任何對象執行個體的引用計數器減1.

但是呢,引用計數有個緻命的問題,就是不能解決循環引用的執行個體,比如A引用了B,B引用了A,連個計數永遠不為0,那麼兩個對象永遠不會回收

可達性分析
可達性分析:這是java裡面采用的方法。該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜尋,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定為不可達的對象不一定就會成為可回收對象。被判定為不可達的對象要成為可回收對象必須至少經曆兩次标記過程,如果在這兩次标記過程中仍然沒有逃脫成為可回收對象的可能性,則基本上就真的成為可回收對象了。
JVM:Garbage Collection GC

呐,就從這個圖的GC Root開始找,根據離散數學裡面的圖一樣,有很多的GC Root

GC Root:

1.虛拟機棧中引用的對象(本地變量表)

2.方法區中靜态屬性引用的對象

3. 方法區中常量引用的對象

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

關于這種引用有四種,強引用,軟引用,弱引用,虛引用

強引用: 是java中最強的引用,可以直接通路指向的對象,有點兒像C裡面的指針,但是它甯願抛出OOM,也不願意去回收,是以可能會導緻記憶體洩露。

軟引用: 僅次于強引用,當記憶體資源使用緊張時才會去回收

弱引用: 弱引用是一種比軟引用較弱的引用類型。在系統GC時,隻要發現弱引用,不管系統堆空間是否足夠,都會将對象進行回收。在java中,可以用java.lang.ref.WeakReference執行個體來儲存對一個Java對象的弱引用。

虛引用: 當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在垃圾回收後,銷毀這個對象,将這個虛引用加入引用隊列。程式可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否将要被垃圾回收。如果程式發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前采取必要的行動。

finalize方法

JVM:Garbage Collection GC
JVM:Garbage Collection GC

引用了别人的圖檔:看看

JVM:Garbage Collection GC

垃圾回收算法🦢

1⃣️.Mark-Sweep(标記-清除)算法

JVM:Garbage Collection GC

分為了兩個階段:第一個階段就是将要回收的對象标記。第二個階段就是将标記的對象回收。看似很簡單,但是很大的問題,就是記憶體碎片了。

JVM:Garbage Collection GC

2⃣️.Copying(複制)算法

可以說,是為了解決上面的記憶體碎片的問題把。把堆記憶體分成了兩部分,一部分是配置設定對象,另一部分留着複制。當要回收的時候,先将還存活的對象複制到另一半,然後一次性清除原來的一半。但是呢,這樣我們能夠使用的記憶體就隻有一半了,另一半根本使用不了,隻能留着複制,而且每次複制都移動那麼多對象,效率明顯會下降。

JVM:Garbage Collection GC

3⃣️.Mark-Compact(标記-整理)算法

這個方法是為了解決Copy算法記憶體使用問題,它是進行垃圾回收後,再把存活的對象往一邊移動,這樣子就可以使用整個記憶體了

JVM:Garbage Collection GC

4⃣️.Generational Collection(分代收集)算法

這是大部分JVM常用的一種垃圾回收算法。首先要看到上面那個堆Heap區域的圖,是以需要先明白Heap區域分成了Young Generation(Eden,S0,S1),Old Generation。

可以看到在Young Generation,将記憶體區域分成了幾塊,很明顯就可以使用Copy算法,也就是說Generational Collection采用了上面三種的某幾種算法,結合而成的。在Young Generation,這樣子在Eden區域的存活的對象複制到S0,然後将Eden區域給清除掉。同理S0存活的對象複制到S1,然後将S0清除。S1存活的複制到老年代Old Generation ,然後清除S1.

JVM:Garbage Collection GC

Young Generation觸發的GC叫做Minor GC,發生頻率比較高,而Old Generation叫做Major GC即Full GC,一般發生頻率比較低,因為都是存活率很久的對象

JVM:Garbage Collection GC

垃圾收集器🆚

主要的垃圾收集器有四種:

  • Serial Garbage Collection:串行垃圾回收器
  • Parallel Garbage Collection:并行垃圾回收器
  • CMS Garbage Collection:CMS垃圾回收其
  • G1 Garbage Collection:G1垃圾回收器
    JVM:Garbage Collection GC

Serial Garbage Collection:一次隻開一個線程回收,在垃圾回收時會停止所有的應用程式線程,不然怎麼會叫串行呢,對吧,看上圖最左邊,應用程式線程停止了,隻有垃圾回收線程在執行。-XX:+UseSerialGCJVM參數以使用串行垃圾收集器。

Parallel Garbage Collection:上圖中間的那個,多個線程執行垃圾回收,是JVM預設的方式,但是也會暫停其他應用程式線程

CMS:并發标記清除(CMS)垃圾收集器使用多個線程掃描堆記憶體,以标記要逐出的執行個體,然後清除标記的執行個體。CMS垃圾收集器隻儲存以下兩種情況下的所有應用程式線程:

  • 在Old Generaton空間中标記引用對象時。
  • 2:如果在執行垃圾收集時堆記憶體發生并行更改。

    與并行垃圾收集器相比,CMS收集器使用更多的CPU來確定更好的應用程式吞吐量。如果我們可以配置設定更多的CPU以獲得更好的性能,那麼CMS垃圾收集器是優于并行收集器的首選。打開XX:+USeParNewGC JVM參數以使用CMS垃圾收集器

    G1 Garbage Collection:G1垃圾收集器用于大型堆記憶體區域。它将堆記憶體分成多個區域,并在這些區域内并行進行收集。G1也會在回收記憶體後壓縮空閑堆空間。但是CMS垃圾收集器壓縮了stop-the-world(STW)情況下的記憶體。G1收集器首先根據大多數垃圾對區域進行優先級排序。打開–XX:+UseG1GC JVM參數以使用G1垃圾收集器。

雖然說分成了上面幾種,但是呢,根據老年代,新生代又會有不同的使用方式。下圖中,Young Generation和Tunured Generation的連線代表可以搭配使用的

JVM:Garbage Collection GC

1:Serial收集器(複制Copy算法)

單線程串行運作,會暫停應用程式。算是曆史很悠久的一種收集器了。

JVM:Garbage Collection GC

2:ParNew收集器(複制Copy算法)

其實就是上面Serial的多線程版本,除了GC線程在多個CPU上同時執行外,其他和Serial沒啥差別,也會要暫停其他應用程式

JVM:Garbage Collection GC

3:Parallel Scavenge(複制Copy算法)

與吞吐量關系密切,故也稱為吞吐量優先收集器。特點:屬于新生代收集器也是采用複制算法的收集器,又是并行的多線程收集器(與ParNew收集器類似)。

該收集器的目标是達到一個可控制的吞吐量。還有一個值得關注的點是:GC自适應調節政策(與ParNew收集器最重要的一個差別)

GC自适應調節政策:Parallel Scavenge收集器可設定-XX:+UseAdptiveSizePolicy參數。當開關打開時不需要手動指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRation)、晉升老年代的對象年齡(-XX:PretenureSizeThreshold)等,虛拟機會根據系統的運作狀況收集性能監控資訊,動态設定這些參數以提供最優的停頓時間和最高的吞吐量,這種調節方式稱為GC的自适應調節政策。

Parallel Scavenge收集器使用兩個參數控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停頓時間
  • XX:GCRatio 直接設定吞吐量的大小。

4:Serial Old收集器(标記整理算法)

在老年代使用的,也是單線程的。使用場景:

  • 在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用。
  • 作為CMS收集器的後備方案,在并發收集Concurent Mode Failure時使用。
    JVM:Garbage Collection GC

5:Parallel Old 收集器(标記-整理算法)

是Parallel Scavenge收集器的老年代版本。

特點:多線程,采用标記-整理算法。

應用場景:注重高吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old 收集器。

Parallel Scavenge/Parallel Old收集器工作過程圖:

JVM:Garbage Collection GC

6:CMS收集器(标記清除算法)

一種以擷取最短回收停頓時間為目标的收集器。

特點:基于标記-清除算法實作。并發收集、低停頓。

應用場景:适用于注重服務的響應速度,希望系統停頓時間最短,給使用者帶來更好的體驗等場景下。如web程式、b/s服務。

CMS收集器的運作過程分為下列4步:

  • 初始标記:标記GC Roots能直接到的對象。速度很快但是仍存在Stop The World問題。
  • 并發标記:進行GC Roots Tracing 的過程,找出存活對象且使用者線程可并發執行。
  • 重新标記:為了修正并發标記期間因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄。仍然存在Stop The World問題。
  • 并發清除:對标記的對象進行清除回收。

CMS收集器的記憶體回收過程是與使用者線程一起并發執行的。

JVM:Garbage Collection GC

CMS收集器的缺點:

  • 對CPU資源非常敏感。
  • 無法處理浮動垃圾,可能出現Concurrent Model Failure失敗而導緻另一次Full GC的産生。
  • 因為采用标記-清除算法是以會存在空間碎片的問題,導緻大對象無法配置設定空間,不得不提前觸發一次Full GC。

7:G1收集器

特點如下:

  • 并行與并發:G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU來縮短Stop-The-World停頓時間。部分收集器原本需要停頓Java線程來執行GC動作,G1收集器仍然可以通過并發的方式讓Java程式繼續運作。
  • 分代收集:G1能夠獨自管理整個Java堆,并且采用不同的方式去處理新建立的對象和已經存活了一段時間、熬過多次GC的舊對象以擷取更好的收集效果。
  • 空間整合:G1運作期間不會産生空間碎片,收集後能提供規整的可用記憶體。
  • 可預測的停頓:G1除了追求低停頓外,還能建立可預測的停頓時間模型。能讓使用者明确指定在一個長度為M毫秒的時間段内,消耗在垃圾收集上的時間不得超過N毫秒。
    JVM:Garbage Collection GC

上面簡單介紹了一些垃圾回收的基礎,後面會詳細的分析垃圾收集器并配置實操