'深度分析Java的垃圾回收過程'

"

Java內存模型

Java把內存大概分為:方法區,虛擬機棧,本地方法棧,堆和程序計數器5個區。

程序計數器 是一塊比較小的內存空間,可以看成是一個線程的運行的指示器,是一個線程私有的內存。由於JVM的運行時通過線程搶佔CPU的時間片實現的(在任何一個時刻,一個CPU只能執行一個指令),如果存在線程的上下文切換,每個線程在被移除時間片的時候都需要記錄執行的位置。如果當前正在執行的是Native方法,則不需要計數器來標記。

虛擬機棧 也是線程私有的內存,Java代碼在運行的時候,會對每一個方法創建一個棧幀,用於存儲局部變量,操作數據,動態鏈接等數據。

本地方法棧 和虛擬機棧作用類似,本地方法棧是為了Java調用Native方法服務。

Java堆 是Java常用的引用對象的真實存儲的區域,也是修改和訪問最為頻繁的區域。

Java的內存模型分類大致如下:

"

Java內存模型

Java把內存大概分為:方法區,虛擬機棧,本地方法棧,堆和程序計數器5個區。

程序計數器 是一塊比較小的內存空間,可以看成是一個線程的運行的指示器,是一個線程私有的內存。由於JVM的運行時通過線程搶佔CPU的時間片實現的(在任何一個時刻,一個CPU只能執行一個指令),如果存在線程的上下文切換,每個線程在被移除時間片的時候都需要記錄執行的位置。如果當前正在執行的是Native方法,則不需要計數器來標記。

虛擬機棧 也是線程私有的內存,Java代碼在運行的時候,會對每一個方法創建一個棧幀,用於存儲局部變量,操作數據,動態鏈接等數據。

本地方法棧 和虛擬機棧作用類似,本地方法棧是為了Java調用Native方法服務。

Java堆 是Java常用的引用對象的真實存儲的區域,也是修改和訪問最為頻繁的區域。

Java的內存模型分類大致如下:

深度分析Java的垃圾回收過程

垃圾回收算法


標記清除方法

標記清除算法是最基礎的算法,算法分為“標記”和“清除”兩個階段: 先標記出需要回收的對象,然後再統一回收所有標記的對象。

標記清除算法的缺點主要有兩個:一個效率比較慢,一個是會產生內存碎片。碎片太多會導致在分配較大對象的時候,無法找到連續的空間,而不得不提前觸發一次垃圾回收。


複製算法

為了解決標記清除法的效率問題,出現了複製算法。把內存分為大小相同的兩個部分。使用的時候,只是用其中的一塊,每次進行垃圾回收的時候,就把存活的對象複製到另外一塊內存,然後把當前的內存區域直接清空。

複製算法比較簡單高效,但是帶來的後果是內存的浪費,可使用的內存只有原來的半。


標記整理算法

結合上面兩種算法的有點和缺點,標記整理的算法就能夠很好的解決浪費和內存的碎片問題。

標記整理的算法,標記過程與標記清除算法相同,只是在回收的時候,把存活得對象都向一側移動,然後直接清理掉邊界以外的內存。

標記整理雖然解決了內存的碎片化的問題,但還是沒有解決性能問題。


分代收集算法

分代收集算法是一個綜合算法,根據不同對象的生存週期而採用上面三種算法。

Java一般把堆內存分為老年代和新生代,新生代存活率低,只需要少量存活得空間就可以了回收,則可以使用複製算法。

老年代存活率高,沒有額外的空間擔保,就只能是由標記整理或者標記清除方法。


HotSpot垃圾回收算法

在Hotspot的虛擬機中,如何實現垃圾內存的標記,首先我們需要理解一個概念:GC Root節點和可達性分析。

GC Root節點包含以下幾種類型:

 1. 虛擬機棧(棧幀中的本地變量表)中引用的對象。
2. 本地方法棧中JNI(即一般說的native方法)引用的對象。
3. 方法區中的靜態變量和常量引用的對象。

在Java中判斷一個對象的是否存活,都是通過判斷這個對象是否具有可達性,即變量是否被GC Root節點引用。

在HotSpot的虛擬機中,使用一組ooPMap的數據結構來記錄對象的引用關係。

Java中常用的收集器


1.Serial收集器

Serial是最基本的,時間最長的垃圾收集器,採用複製算法回收。Serial在JDK1.3以前都已經在使用了,從名字可以看出Serial收集器是單線程工作的。單線程帶來的問題是在程序在垃圾回收的時候,會出現停頓。 Serial收集器在有的場景中的有點點也很多,由於沒有cpu上下文的切換,是的Serial收集器相對比較簡單高效,短暫的暫停只要不是過於頻繁,還是能夠被接受的。


2.ParNew收集器

ParNew是Serial收集器的多線程版本,可以通過XX:ParallelGCThread參數來控制會受到的線程數,在單個CPU的環境下,由於存在線程的上下文的切換,所以性能不一定能保證優於Serial收集器。


3.Parallel Scavenge 收集器

Parallel Scavenge 收集器是新生代的收集器,也是採用複製算法。和其他收集器不同的是,Parallel Scavenge 收集器只關心吞吐量,而不關心GC的暫停時間。 舉一個簡單的場景,如果一個垃圾回收過程,一次GC需要1s,如果分成4次,每次需要0.5s,兩次GC的時間分別是1s和2s,對於程序的體驗來後,後者的GC時間的停頓間隔低於前者,大多數GC回收期都會採用後面的回收機制,而對於Parallel Scavenge 收集器會選擇前者,而不會選多次回收來降低GC的停頓時間。


4.Serial Old 收集器

Serial Old 收集器是Serial收集器的老年代的版本,同樣是一個單線程版本,採用標記整理算法


5.Parallel Old收集器

Parallel Old收集器是 Parallel Scavenge 收集器的一個老年代版本,採用標記整理算法。在JDK1.6版本中才開始提供,在此之前,如果新生代採用了Parallel Scavenge收集器,老年代回收除了Serial收集器之外別無選擇。由於Serial收集器的效率拖累,所以Parallel Old收集器應運而生。


6.CMS收集器

CMS收集器是一種以獲取最短回收停頓時間為目標的收集器,大部分運行在BS的服務端。CMS收集器主要有4個步驟:

  1. 初始標記
  2. 併發標記
  3. 重新標記
  4. 併發清除

初始標記是僅僅標記一下GC Roots直接關聯到的對象,速度很快。併發標記就是進行GC Roots Tracing的過程,重新標記是為了修正有變動的的對象。在初始標記和重新標記的時候,需要“Stop the world”。


7.G1 收集器

G1收集器是最新的收集器,也是面向服務端的垃圾收集器,G1具有一下幾個特點:

  1. 併發和並行
  2. 分代手機
  3. 空間整合
  4. 可預測的停頓

G1 收集器大致分為下面幾個步驟:

  1. 初始標記
  2. 併發標記
  3. 最終標記
  4. 篩選回收

與CMS對比,最終標記和CMS重新標記相同,不同的在於篩選回收,篩選回收是先對各個回收的Region的回收價值和成本進行綜合排序,根據用戶的期望來進行回收。

內存分配和垃圾回收的策略


在Java的堆內存中可以簡單把內存分為如下結構:

"

Java內存模型

Java把內存大概分為:方法區,虛擬機棧,本地方法棧,堆和程序計數器5個區。

程序計數器 是一塊比較小的內存空間,可以看成是一個線程的運行的指示器,是一個線程私有的內存。由於JVM的運行時通過線程搶佔CPU的時間片實現的(在任何一個時刻,一個CPU只能執行一個指令),如果存在線程的上下文切換,每個線程在被移除時間片的時候都需要記錄執行的位置。如果當前正在執行的是Native方法,則不需要計數器來標記。

虛擬機棧 也是線程私有的內存,Java代碼在運行的時候,會對每一個方法創建一個棧幀,用於存儲局部變量,操作數據,動態鏈接等數據。

本地方法棧 和虛擬機棧作用類似,本地方法棧是為了Java調用Native方法服務。

Java堆 是Java常用的引用對象的真實存儲的區域,也是修改和訪問最為頻繁的區域。

Java的內存模型分類大致如下:

深度分析Java的垃圾回收過程

垃圾回收算法


標記清除方法

標記清除算法是最基礎的算法,算法分為“標記”和“清除”兩個階段: 先標記出需要回收的對象,然後再統一回收所有標記的對象。

標記清除算法的缺點主要有兩個:一個效率比較慢,一個是會產生內存碎片。碎片太多會導致在分配較大對象的時候,無法找到連續的空間,而不得不提前觸發一次垃圾回收。


複製算法

為了解決標記清除法的效率問題,出現了複製算法。把內存分為大小相同的兩個部分。使用的時候,只是用其中的一塊,每次進行垃圾回收的時候,就把存活的對象複製到另外一塊內存,然後把當前的內存區域直接清空。

複製算法比較簡單高效,但是帶來的後果是內存的浪費,可使用的內存只有原來的半。


標記整理算法

結合上面兩種算法的有點和缺點,標記整理的算法就能夠很好的解決浪費和內存的碎片問題。

標記整理的算法,標記過程與標記清除算法相同,只是在回收的時候,把存活得對象都向一側移動,然後直接清理掉邊界以外的內存。

標記整理雖然解決了內存的碎片化的問題,但還是沒有解決性能問題。


分代收集算法

分代收集算法是一個綜合算法,根據不同對象的生存週期而採用上面三種算法。

Java一般把堆內存分為老年代和新生代,新生代存活率低,只需要少量存活得空間就可以了回收,則可以使用複製算法。

老年代存活率高,沒有額外的空間擔保,就只能是由標記整理或者標記清除方法。


HotSpot垃圾回收算法

在Hotspot的虛擬機中,如何實現垃圾內存的標記,首先我們需要理解一個概念:GC Root節點和可達性分析。

GC Root節點包含以下幾種類型:

 1. 虛擬機棧(棧幀中的本地變量表)中引用的對象。
2. 本地方法棧中JNI(即一般說的native方法)引用的對象。
3. 方法區中的靜態變量和常量引用的對象。

在Java中判斷一個對象的是否存活,都是通過判斷這個對象是否具有可達性,即變量是否被GC Root節點引用。

在HotSpot的虛擬機中,使用一組ooPMap的數據結構來記錄對象的引用關係。

Java中常用的收集器


1.Serial收集器

Serial是最基本的,時間最長的垃圾收集器,採用複製算法回收。Serial在JDK1.3以前都已經在使用了,從名字可以看出Serial收集器是單線程工作的。單線程帶來的問題是在程序在垃圾回收的時候,會出現停頓。 Serial收集器在有的場景中的有點點也很多,由於沒有cpu上下文的切換,是的Serial收集器相對比較簡單高效,短暫的暫停只要不是過於頻繁,還是能夠被接受的。


2.ParNew收集器

ParNew是Serial收集器的多線程版本,可以通過XX:ParallelGCThread參數來控制會受到的線程數,在單個CPU的環境下,由於存在線程的上下文的切換,所以性能不一定能保證優於Serial收集器。


3.Parallel Scavenge 收集器

Parallel Scavenge 收集器是新生代的收集器,也是採用複製算法。和其他收集器不同的是,Parallel Scavenge 收集器只關心吞吐量,而不關心GC的暫停時間。 舉一個簡單的場景,如果一個垃圾回收過程,一次GC需要1s,如果分成4次,每次需要0.5s,兩次GC的時間分別是1s和2s,對於程序的體驗來後,後者的GC時間的停頓間隔低於前者,大多數GC回收期都會採用後面的回收機制,而對於Parallel Scavenge 收集器會選擇前者,而不會選多次回收來降低GC的停頓時間。


4.Serial Old 收集器

Serial Old 收集器是Serial收集器的老年代的版本,同樣是一個單線程版本,採用標記整理算法


5.Parallel Old收集器

Parallel Old收集器是 Parallel Scavenge 收集器的一個老年代版本,採用標記整理算法。在JDK1.6版本中才開始提供,在此之前,如果新生代採用了Parallel Scavenge收集器,老年代回收除了Serial收集器之外別無選擇。由於Serial收集器的效率拖累,所以Parallel Old收集器應運而生。


6.CMS收集器

CMS收集器是一種以獲取最短回收停頓時間為目標的收集器,大部分運行在BS的服務端。CMS收集器主要有4個步驟:

  1. 初始標記
  2. 併發標記
  3. 重新標記
  4. 併發清除

初始標記是僅僅標記一下GC Roots直接關聯到的對象,速度很快。併發標記就是進行GC Roots Tracing的過程,重新標記是為了修正有變動的的對象。在初始標記和重新標記的時候,需要“Stop the world”。


7.G1 收集器

G1收集器是最新的收集器,也是面向服務端的垃圾收集器,G1具有一下幾個特點:

  1. 併發和並行
  2. 分代手機
  3. 空間整合
  4. 可預測的停頓

G1 收集器大致分為下面幾個步驟:

  1. 初始標記
  2. 併發標記
  3. 最終標記
  4. 篩選回收

與CMS對比,最終標記和CMS重新標記相同,不同的在於篩選回收,篩選回收是先對各個回收的Region的回收價值和成本進行綜合排序,根據用戶的期望來進行回收。

內存分配和垃圾回收的策略


在Java的堆內存中可以簡單把內存分為如下結構:

深度分析Java的垃圾回收過程

1.對象優先在Eden分配

在大多數情況下,對象的分配優先在新生代Eden中分配,當Eden沒有足夠的空間進行分配的時候虛擬機觸發一次Minor GC。


2.大對象直接進入老年代

大對象通常是指很長字符串和數組,新生代的內存無法安置,就直接進入老年代空間盡心 存放,大對象內存的閾值可以用-XX:PretenureSizeThreshold參數來定義。


3.長期存活的對象進入老年代

對象在Eden中出生,並經歷了一次MinorGC後仍然存活,並且能夠被Survivor存放的話,將會把對象從Eden區域移動到S1區域,年代就記為1歲。當年齡增加到一定(默認是15歲)就會被晉升到老年代,這個閾值可以通過-XX:MaxTenuringThreshold設置。

為了更好的適應不同程序的內存情況,虛擬機並不是簡單的要求對象的年齡必須達到某個閾值,如果在Survivor空間中相同年齡所有對象的大小綜合大於Survivor空間的一半,則年齡大於或等於這個年齡的對象可以直接進入老年代。


4.空間分配擔保

為了保證Minor GC能夠順利執行,虛擬機會在MinorGC 回收前檢查老年代最大可用的連續內存空間是否大於新生代所有對象總和,如果條件成立,該次MinorGC可以安全執行。

如果不成立,虛擬機會查看是否允許擔保失敗,如果允許,則虛擬機會繼續檢查可用空間是否大於歷次晉升到老年代的平均水平,如果大於,則嘗試進行一次MinorGC,顯然這次回收是有風險的,如果分配失敗則會重新觸發一次FULL GC。

如果虛擬機設置不允許擔保失敗,則會進行一次FULL GC。

(本文完)

點擊右上角關注作者,加關注不迷路,歡迎交流

"

相關推薦

推薦中...