深入Java垃圾收集
1. 判斷物件是否已死的方法
- 可達性分析
可作為GC Roots的物件包括下面幾種:
- 虛擬機器棧(棧幀中的本地變量表)中引用的物件。
- 方法區中類靜態屬性引用的物件。
- 方法區中常量引用的物件。
- 本地方法棧中JNI(即一般說的Native方法)引用的物件。
2. 垃圾收集演算法:
-
標記-清除(Mark Sweep)演算法:
分標記和清除兩個階段,缺點:一是兩個階段效率低,二是產生記憶體碎片。
-
複製(Copying)演算法:
把記憶體平均分為兩塊,每次只使用其中一塊,當這一塊的記憶體使用完了就將還存活的物件複製到另外一塊上面,然後在把使用過的記憶體空間一次清理掉。
特點:實現簡單、執行高效。缺點是:每次只使用一半記憶體,浪費記憶體,而且如果存活物件比較多則需要複製很多物件,效率會變低。 -
標記-整理(Mark-Compact)演算法:
標記過程與標記-清除(Mark Sweep)演算法一樣,然後讓存活的物件向一端移動,然後直接清理掉邊界以外的記憶體。
- 分代收集演算法:
大多數商業虛擬機器都採用這種演算法,一般把Java堆分為新生代和老年代,根據各個年代的特點採取適當的收集演算法,一般新生代採用複製演算法,老年代採用標記-清理或標記-整理。
3. 垃圾收集器:
-
Serial收集器
新生代收集器,採用複製演算法。
特點:簡單高效,Client模式下預設的垃圾收集器。
缺點:單執行緒進行垃圾收集,收集期間發生STW(Stop The World)暫停所有使用者執行緒。 -
ParNew收集器
Serial收集器的多執行緒版本。使用複製演算法,新生代收集器,除了Serial之外唯一一個可以搭配CMS的收集器。
隨著可以使用的CPU數量的增加,它對於GC時系統資源的有效利用還是很有好處的。它預設開啟的收集執行緒數與CPU的數量相同,在CPU非常多的環境下,可以使使用-XX:ParallelGCThreads引數來限制來限制垃圾收集的執行緒數。
-
Parallel Scavenge收集器
是一個新生代收集器,使用複製演算法,並行的多執行緒收集器。關注吞吐量,目的為達到一個可控的吞吐量,可通過引數-XX:MaxGCPauseMillis控制最大停頓時間,單位毫秒,引數-XX:GCTimeRatio控制垃圾收集時間佔總時間佔比,值為大於0小於100的整數。引數-XX:UseAdaptiveSizePolicy開啟後可以自動調節新生代大小、Eden與Survivor區的比例以及晉升老年代物件大小。
-
Serial Old收集器
Serial收集器的老年代版本,單執行緒收集器,標記-整理演算法進行收集,主要用於Client模式下。
-
Parallel Old收集器
Parallel Scavenge收集器的老年代版本。多執行緒,使用標記-整理演算法。
- CMS收集器
年老代收集器,一種以獲取最短回收停頓時間為目標的收集器。基於“標記-清除”演算法。收集過程包含如下四個步驟:
1) 初始標記
2) 併發標記
3) 重新標記
4) 併發清除
其中1)、3)需要STW,但耗時很少,另外兩個耗時的過程收集執行緒可以和使用者執行緒一起工作。所以整體來說CMS收集器的記憶體回收過程是與使用者執行緒一起併發執行的。特點:併發收集(整體來說收集執行緒和使用者執行緒一起工作)、低停頓。
缺點:- 和使用者執行緒一起併發工作時會佔用一些CPU資源.
- 無法處理浮動垃圾,浮動垃圾是指在和使用者執行緒一起工作時使用者執行緒產生的垃圾無法回收,考慮到會和使用者執行緒一起工作所以會預留一部分(可配百分比)記憶體給使用者執行緒,在收集期間如果這部分預留的記憶體不滿足使用者執行緒就會導致“Concurrent Mode Failure”,從而使JVM啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集。
- 因為基於“標記-清除”演算法所以會產生記憶體碎片。
4 GC 日誌
- -XX:+UseSerialGC
GC收集器:
新生代:Serial
老年代:SerialOld新生代回收日誌: [GC (Allocation Failure): [DefNew: 1664K->0K(1856K), 0.0001900 secs] 2105K->441K(5952K), 0.0002271 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
老年代回收日誌:
[Full GC (Allocation Failure) [Tenured: 4095K->4095K(4096K), 0.0096600 secs] 5951K->5544K(5952K), [Metaspace: 3373K->3373K(1056768K)], 0.0096927 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation total 1856K, used 899K [0x00000007bfa00000, 0x00000007bfc00000, 0x00000007bfc00000)
eden space 1664K, 54% used [0x00000007bfa00000, 0x00000007bfae0bf8, 0x00000007bfba0000)
from space 192K, 0% used [0x00000007bfba0000, 0x00000007bfba0048, 0x00000007bfbd0000)
to space 192K, 0% used [0x00000007bfbd0000, 0x00000007bfbd0000, 0x00000007bfc00000)
tenured generation total 4096K, used 441K [0x00000007bfc00000, 0x00000007c0000000, 0x00000007c0000000)
the space 4096K, 10% used [0x00000007bfc00000, 0x00000007bfc6e740, 0x00000007bfc6e800, 0x00000007c0000000)
Metaspace used 3378K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
2. -XX:+UseParallelGC
> GC收集器:
> 新生代:Parallel Scavenge
> 老年代:SerialOld
新生代回收日誌:
[GC (Allocation Failure) [PSYoungGen: 1056K->64K(1536K)] 1553K->561K(5632K), 0.0005464 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
老年代回收日誌:
[Full GC (Ergonomics) [PSYoungGen: 1024K->981K(1536K)] [ParOldGen: 3870K->3870K(4096K)] 4894K->4852K(5632K), [Metaspace: 3371K->3371K(1056768K)], 0.0131815 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 1536K, used 309K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 24% used [0x00000007bfe00000,0x00000007bfe3d7d0,0x00000007bff00000)
from space 512K, 12% used [0x00000007bff00000,0x00000007bff10000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 497K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 12% used [0x00000007bfa00000,0x00000007bfa7c530,0x00000007bfe00000)
Metaspace used 3378K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
3. -XX:+UseConcMarkSweepGC
> GC收集器:
> 新生代:ParNew
> 老年代:CMS
新生代回收日誌:
[GC (Allocation Failure) 11.273: [ParNew: 1090K->2K(1216K), 0.0014268 secs] 1546K->458K(6016K), 0.0015251 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
老年代回收日誌:
[Full GC (Allocation Failure) [CMS: 4800K->4800K(4800K), 0.0179098 secs] 6015K->6015K(6016K), [Metaspace: 3372K->3372K(1056768K)], 0.0179558 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
Heap
par new generation total 1216K, used 35K [0x00000007bfa00000, 0x00000007bfb50000, 0x00000007bfb50000)
eden space 1088K, 3% used [0x00000007bfa00000, 0x00000007bfa08588, 0x00000007bfb10000)
from space 128K, 1% used [0x00000007bfb30000, 0x00000007bfb30810, 0x00000007bfb50000)
to space 128K, 0% used [0x00000007bfb10000, 0x00000007bfb10000, 0x00000007bfb30000)
concurrent mark-sweep generation total 4800K, used 456K [0x00000007bfb50000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 3376K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
4. -XX:+UseParNewGC(java8下提示過時)
> GC收集器:
> 新生代:ParNew
> 老年代:SerialOld
8.506: [GC (Allocation Failure) 8.506: [ParNew: 1666K->2K(1856K), 0.0002354 secs] 2214K->550K(5952K), 0.0002726 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 1856K, used 1400K [0x00000007bfa00000, 0x00000007bfc00000, 0x00000007bfc00000)
eden space 1664K, 84% used [0x00000007bfa00000, 0x00000007bfb5db38, 0x00000007bfba0000)
from space 192K, 1% used [0x00000007bfba0000, 0x00000007bfba0810, 0x00000007bfbd0000)
to space 192K, 0% used [0x00000007bfbd0000, 0x00000007bfbd0000, 0x00000007bfc00000)
tenured generation total 4096K, used 548K [0x00000007bfc00000, 0x00000007c0000000, 0x00000007c0000000)
the space 4096K, 13% used [0x00000007bfc00000, 0x00000007bfc89268, 0x00000007bfc89400, 0x00000007c0000000)
Metaspace used 3378K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
5. 解釋
> GC (Allocation Failure):表示新生代發生的垃圾回收日誌。
> Full GC (Allocation Failure):表示老年代和元空間發生的垃圾回收日誌。
>
> [gc收集器名稱:gc前該記憶體空間使用量-> gc後該記憶體空間使用量(該記憶體空間總大小)]
### 5. 記憶體分配與回收策略
1. 物件優先在Eden分配
> 大多數情況下,物件在新生代Eden中分配。當Eden沒有足夠空間進行分配時,虛擬機器將發起一次Minor GC。預設 Eden: From Survivor: To Survivor = 8:1:1
2. 大物件直接進入老年代
> 大物件是指需要大量連續空間的Java物件,最典型的就是很長的字串以及陣列。引數-XX:PretenureSizeThreshold可以指定這個閾值。
3. 長期存活的物件將進入老年代
> 虛擬機器給每個物件定義了一個物件年齡(Age)計數器。如果物件在Eden出生並經歷過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設定為1。物件在Survivor區中每“熬過”一次Minor GC,年齡就增加1,當它的年齡達到一定程度(預設15),將會被晉升到老年代。物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold設定。
4. 動態物件年齡判定
> 為了能更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件的年齡必須達到MaxTenuringThreshold才能晉升到老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。