1. Java堆內存結構
Java將堆內存分為3大部分:新生代、老年代和永久代,其中新生代又進一步划分為Eden、S0、S1(Survivor)三個區。結構如下圖所示:
1. Java堆內存結構
Java將堆內存分為3大部分:新生代、老年代和永久代,其中新生代又進一步划分為Eden、S0、S1(Survivor)三個區。結構如下圖所示:
程序中new出來的對象會在新生代里的Eden區里面分配空間,如果存活時間足夠長將會進入Survivor區,進而如果存活時間再長,還會被提升分配到老年代里面。持久代里面存放的是Class類元數據、方法描述等。
1.S0和S1是兩個大小相等的區域,分配內存空間只會在其中某一個進行,另外一個空間是用來輔助進行新生代進行垃圾回收的,因為新生代的垃圾回收策略基於復制算法,其思想是將Eden區及兩個Survivor中的某個區,如S0區里面需要存活的對象復制到另外一個空的Survivor區,如S1區,然后就可以回收Eden和S0區域里面的死亡對象。下一次回收就對調S0和S1兩個區的角色,S1用來存放存活對象而S0用來輔助回收垃圾,如此循環利用。
2.有些文章並不將永久代納入Java堆內存。其實永久代就是我們所說的方法區,而方法區經常被稱為Non-Heap(非堆)。僅僅在HotSpot虛擬機的實現中才將GC分代收集擴展至方法區,或者說使用永久代來實現方法區,對於其他的虛擬機是不存在永久代這個概念的。
3.並非所有的對象創建都會在Eden區中分配內存空間。對於Serial和ParNew垃圾收集器,通過指定-XX:PretenureSizeThreshold={size}來設置超過這個閾值大小的對象直接進入老年代。
2. 分代回收算法
垃圾回收主要針對Java堆內存中的新生代和老年代,也正因為新生代和老年代結構上的不同,所以產生了分代回收算法,即新生代的垃圾回收和老年代的垃圾回收采用的是不同的回收算法。針對新生代,主要采用復制算法,而針對老年代,通常采用標記-清除算法或者標記-整理算法來進行回收。
2.1 復制算法
復制算法的思想是將內存分成大小相等的兩塊區域,每次使用其中的一塊。當這一塊的內存用完了,就將還存活的對象復制到另一塊區域上,然后對該塊進行內存回收。
這個算法實現簡單,並且也相對高效,但是代價就是需要將犧牲一半的內存空間用於進行復制。有研究表明,新生代中的對象98%存活期很短,所以並不需要以1:1的比例來划分整個新生代,通常的做法是將新生代內存空間划分成一塊較大的Eden區和兩塊較小的Survivor區,兩塊Survivor區域的大小保持一致。每次使用Eden和其中一塊Survivor區,當回收的時候,將還存活的對象復制到另外一塊Survivor空間上,最后清除Eden區和一開始使用的Survivor區。假如用於復制的Survivor區放不下存活的對象,那么會將對象存到老年代。
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1:1,也就是說新生代中犧牲掉10%的空間而不是一半的空間。
2.2 標記-清除算法
標記-清除(Mark-Sweep)算法分為兩個階段:
標記
清除
在標記階段將標記出需要回收的對象空間,然后在下一個階段清除階段里面,將這些標記出來的對象空間回收掉。這種算法有兩個主要問題:一個是標記和清除的效率不高,另一個問題是在清理之后會產生大量不連續的內存碎片,這樣會導致在分配大對象時候無法找到足夠的連續內存而觸發另一次垃圾收集動作。
2.3 標記-整理算法
標記-整理(Mark-Compact)算法有效預防了標記-清除算法中可能產生過多內存碎片的問題。在標記需要回收的對象以后,它會將所有存活的對象空間挪到一起,然后再執行清理。
標記-整理通常會在標記-清除算法里面作為備選方案,為了防止標記-清除后產生大量內存碎片而無法為大對象分配足夠內存的情況
3. 垃圾收集器
因為新生代和老年代采用回收算法的不同,垃圾收集器相應地也分為新生代收集器和老年代收集器。其中新生代收集器主要有Serial收集器、ParNew收集器和Parallel Scavenge收集器。老年代收集器主要有Serial Old收集器、Parallel Old收集器和CMS收集器。當然還包括了一款全新的、新生代老年代通用的G1收集器。
上圖展示了7種作用於不同分代的收集器,如果兩個收集器之間存在連線,就說明它們可以搭配使用。
3.1 新生代收集器
3.1.1 Serial收集器
Serial收集器作用於新生代,是一個單線程收集器,基於復制算法實現。在進行垃圾回收的時候僅使用單條線程並且在回收的過程中會掛起所有的用戶線程(Stop The World)。Serial收集器是JVM client模式下默認的新生代收集器。
特別注意,Stop-The-World會掛起應用線程,造成應用的停頓。
3.1.2 ParNew收集器
ParNew收集器作用於新生代,是一個多線程收集器,基於復制算法實現。相對於Serial收集器而言,在垃圾回收的時候會同時使用多條線程進行回收,但是它跟Serial收集器一樣,在回收過程中也是會掛起所有的用戶線程,從而造成應用的停頓。
3.1.3 Parallel Scavenge收集器
Parallel Scavenge收集器同樣作用於新生代,並且也是采用多線程和復制算法來進行垃圾回收。Parallel Scavenge收集器關注的是吞吐量,即使得應用能夠充分使用CPU。它與ParNew收集器一樣,在回收過程會掛起所有的用戶線程,造成應用停頓。
3.2 老年代收集器
3.2.1 Serial Old收集器
Serial Old收集器作用於老年代,采用單線程和標記-整理算法來實現垃圾回收。在回收垃圾的時候同樣會掛起所有用戶線程,造成應用的停頓。一般來說,老年代的容量都比新生代要大,所以當發生老年代的垃圾回收時,STW經歷的時間會比新生代所用的時間長得多。該收集器是JVM client模式下默認的老年代收集器。
3.2.2 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,采用多線程和標記-整理算法來實現老年代的垃圾回收。這個收集器主要是為了配合Parallel Scavenge收集器的使用,即當新生代選擇了Parallel Scavenge收集器的情況下,老年代可以選擇Parallel Old收集器。
3.2.3 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一款真正實現了並發收集的老年代收集器。CMS收集器以獲取最短回收停頓時間為目標,采用多線程並發以及標記-清除算法來實現垃圾回收。CMS只在初始化標記和重新標記階段需要掛起用戶線程,造成一定的應用停頓(STW),而其他階段收集線程都可以與用戶線程並發交替進行,不必掛起用戶線程,所以並不會造成應用的停頓。CMS收集器可以最大程度地減少因垃圾回收而造成應用停頓的時間。
3.2.4 G1收集器
G1收集器的優勢:
(1)並行與並發
(2)分代收集
(3)空間整理 (標記整理算法,復制算法)
(4)可預測的停頓