首页 > 代码库 > 也学习Java/JVM/GC (三)

也学习Java/JVM/GC (三)

GC算法

目前HotSpot的GC算法是针对分代的GC算法,主要包括串行GC、并行GC、CMS GC和G1。

一、串行GC

串行GC可以在JVM的启动参数上加-XX:+UseSerialGC这个非标准化参数实现。串行GC会暂停应用,进行垃圾回收。多数运行在客户端机器并且 没有短暂停时间要求的应用使用串行GC。串行GC仅利用了一个虚拟的处理器进行垃圾回收。
在应用中使用串行GC命令:
java -Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails JvmTest
二、Parallel GC

并行收集器也叫做吞吐量收集器。因为并行收集器使用了多个cpu能够加快程序的吞吐量。此收集器被用来做大量工作并且长暂停的情况是可以接受的程序。例如打印报表等。
-XX:+UseParallelGC并行GC采用多线程来完成对年青代的垃圾收集,但是然然采用单线程来完成对年老代的垃圾收集。这个选项也会使用单线程对年老代进行压缩。
在N个处理器的机器中,默认有N个垃圾收集器线程,线程的数量可以通过-XX:ParallelGCThreads=设置。
在单CPU的主机上尽管配置了并行的垃圾收集器,但还是会使用默认的垃圾收集器。
在2个CPU的主机上并行垃圾收集作为默认的垃圾收集器,并且在多CPU的主机中会减少年青代GC的暂停时间。并行GC的参数为:-XX:+UseParallelGC。
在应用启动时的命令为:
java -Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC -XX:+PrintGCDetails JvmTest
-XX:+UseParallelOldGC
使用-XX:+UseParallelOldGC选项,JVM会在年青代和年老代同时使用多线程回收。这个选项同时也是多线程压缩收集器。HotSpot会在年老代进行压缩,在年青代复制收集,不需要进行压缩。
注意:-XX:+UseParallelGC用在年青代多线程,年老代单线程,-XX:+UseParallelOldGC用在年青代和年老代都是多线程。

三、CMS GC

全称The Concurrent Mark Sweep (CMS) Collector。CMS是年老代垃圾收集器。CMS试着最小化由并发垃圾收集工作造成的暂停时间。通常情况下,并发低暂停的收集器不会复制或压缩活着的对象。一次垃圾收集不会移动活着的对象。如果有碎片的问题,分配更大的堆。

CMS收集器用来在有低暂停和垃圾收集器共享资源的应用。包含桌面UI响应事件的程序,web服务器响应请求的程序等。

用法:

参数为:-XX:+UseConcMarkSweepGC,设置回收线程数量为:-XX:+ParallelCMSThreads=<desired number>

例如:java -Xms20m -Xmx20m -Xmn10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails JvmTest

四、G1

G1垃圾收集器是一种服务端的垃圾收集器,针对大内存和多核处理器的机器。G1以高的概率满足GC暂停时间,同时又能达到高吞吐量的要求。JDK7 update 4及后续版本完全支持了G1垃圾回收器,G1为以下程序设计:
像CMS收集器一样和应用程序并发执行。
压缩空闲空间不会产生长时间的暂停。
需要可预测的GC暂停时间。
不想牺牲吞吐量。
启动后不需要大的java堆。

G1被作为用来替代CMS的垃圾收集器。对比G1和CMS,它们的不同之处使得G1是一个更好的解决方案。一个不同点是G1是一个压缩型的收集器。G1的压缩能够完全避免使用细微内存空间的分配,而取而代之的时依赖于区。这大大简化了收集器,并且消除了潜在的内存碎片的可能性。同样,相比于CMS,G1提供了可预测的垃圾收集暂停时间,也允许用户提供希望的暂停目标。

G1的内存分布方法:
技术分享

G1垃圾收集器中,堆被划分为许多大小相同的区,每个区都有个连续的虚拟内存。确定区的集合被赋予像老的垃圾收集器相同的角色(eden,survivor,old),但是老的垃圾收集器没有一个固定大小的尺寸。在内存使用上提供了更大的灵活性。

当进行垃圾收集时,G1使用和CMS相同的方式。G1对整个堆进行一次全局并发标记决定对象的存活。标记阶段完成后,G1知道了哪个对是最空的。G1首先收集这些区域,由此产生了大量的空闲空间。这也是为什么这种垃圾回收方法被叫做垃圾优先。顾名思义,G1关注于堆中可能充满可回收对象区域的收集和压缩活动,可回收对象就是所谓的垃圾。G1使用了暂停预测模型来满足用户自定义的暂停时间目标,并且选择基于声明的暂停时间来选择一定数量的区域回收。

被G1识别为成熟的可用于回收的区域使用疏散方法进行垃圾回收。G1从堆中的一个或多个区复制对象到堆中的一个单独区,在这个过程中同时进行压缩和释放内存。在多处理器上这个处理过程是并行执行的,会降低暂停时间和增加吞吐量。这样,每次垃圾回收,G1会在用户用户定义的暂停时间内持续工作来减少内存碎片。比以前的垃圾回收算法强大很多。CMS不会进行压缩。 ParallelOld垃圾收集仅仅执行整个堆的压缩,因此会造成很长时间的暂停。

很重要的一点是G1不是一个实时的垃圾回收器。G1会尽可能的满足设定的暂停时间目标,但是不会保证在给定的时间内完成。根据以前收集的数据,G1会评估在用户设定的目标时间内有多少区可以被回收。这样,收集器有一个精确的回收区耗时计算模型,并且使用这个模型在给定的时间内决定回收哪个或那些区域。
注意:G1有并发(和应用程序一同运行,例如细化,标记,清理)和并行(多线程,例如STW)阶段。fgc仍然是使用单线程,但是经过调优设置可以避免fgc。

G1足迹
如果你将应用从ParallelOldGC或CMS收集到G1,你可能会看到JVM进程占用了更大的内存。这个极大程度和“accounting”数据结构有关,例如Remembered Sets和Collection Sets。

Remembered Sets或RSets跟踪指向某个区域的引用。在堆中每个区中都有一个RSets。Rsets保证了一个区能够进行平行和独立收集。RSets总体影响小于5%。

Collection Sets或CSets,在一次GC中将被收集的集合。在一次GC中所有活着的数据都被转移了(复制/移动)。这个区域集合可能是Eden,survivor和/或old generation。CSets总体占用JVM不到1%。

推荐使用G1的用例
G1首要关注的是为用户运行需要大的堆且低GC等待时间的应用程序提供解决方案。因此需要至少6GB或更大的内存空间,并且固定和可预测的小雨0.5秒暂停时间。

现今运行的应用如果具有以下的一个或几个特点,可以从CMS或ParallelOldGC转移到G1,获得使用G1的好处。
Full GC时间太长或太频繁。
对象分配的频率或代数提升明显。
不希望长时间的垃圾收集或暂停时间(长于0.5或1秒钟)。
注意:如果你正在使用CMS或ParallelOldGC并且你的应用没有经历长时间的垃圾收集暂停时间,停留在目前的收集器也是好的。升级到最新的JDK并不要求更改为G1收集器。

回顾CMS GC

并发标记清理(CMS)收集器(也可以成为并发低暂停收集器)回收年老代内存。CMS的大部分工作和应用程序并发执行,以此来减少由垃圾收集造成的暂停时间。通常情况下并发低暂停垃圾收集器不会复制和压缩活的对象。垃圾回收是在不移动活着的对象完成的。如果有碎片问题,可以分配一个更大的堆。


注意:CMS垃圾收集器在年轻代使用和并行回收收集器相同的算法。


CMS 收集阶段

CMS收集器在年老代中分为以下几个阶段执行

1、初始标记(STW):包括来自年轻代中可达在内的所有年老代的对象根据这些对象的可达情况被标记。相对与MGC的暂停时间通常是短暂的。

2、并发标记:当java程序正在执行时并发的遍历年老代的可达对象图。扫描从被标记的对象开始,直到遍历所有由根部可达的对象。变更器(mutators)在并发的2、3和5阶段被执行,在CMS回收阶段分配内存的对象被立即标记为存活状态。

3、再次标记(STWE):找到在并发标记阶段(第2步)遗漏的对象,这些遗漏的对象是由于在并发标记后应用线程更新的。

4、并发清理:收集在标记阶段被识别为不可达的对象。收集的死对象空间加入到空闲列表,为后续分配内存使用。死对象的合并也可能在此时发生。注意活的对象不会被移动。

5、重置:清理数据结构为下一次并发收集做准备。


CMS GC的步骤
1、CMS 收集器的堆结构
堆被年青代和年老代两部分,年青代又被划分为1个eden区和2个survivor区,年老代则是一块连续的区域。对象收集就地完成。除了fgc外不会进行压缩。
2、在CMS中ygc是如何工作的

年青代是被浅绿色标记,年老代被蓝色标记。如果你的应用已经运行了一段时间看起来就会像图中一样。对象散落在年老代的各个地方。

技术分享

使用CMS,年老代对象被就地回收。它们不会被移动到其它地方。除fgc外年老代不会被压缩

3、年青代的垃圾收集

在eden区和survivor区存活的对象被移动到另外一个survivor区。任何对象达到了最大年龄阀值就会被放到年老代。

技术分享
4、ygc后
ygc后eden和一个survivor区域是空闲的。
技术分享
新晋升到年老代的对象用深蓝色表示,在年青代中存活的绿色对象没有被转移到年老代中。
5、CMS的年老代回收

两侧STW发生时间:初始标记和再标记。当年老代达到了一定的容量比例时,CMS开始执行。

技术分享

(1)初始标记是一个短暂停,活着的(可达的)对象会被标记。(2)当应用程序运行期间并发标记寻找活着的对象。最后(3)再标记阶段,找到在阶段(2)中被错过的对象。

6、年老代回收,并发清理
在前一阶段没有被标记的对象被就地回收,此时没有进行压缩。
技术分享
注意:没有被标记的对象 = 死对象
7、年老代回收--清理之后

在第(4)步清理阶段之后,你可以看到许多空闲的内存。也可以看到空间没有进行压缩。


技术分享

最后,CMS收集器会进行第(5)重置阶段并且等待下次GC阀值到达。


分步了解G1垃圾收集器

G1收集器采用了一种不同的方式分配内存。下面的图例分布描绘了G1系统。

1、G1的堆结构

G1的堆是一快被分成许多固定大小的内存区域。


技术分享

G1堆的大小是在JVM启动的时候设定的。JVM通常设置2000个区,区的大小范围为从1Mb到32Mb。


2、G1堆的分配

实际上,这些区域和逻辑上的eden, survivor, old generation space相当。

技术分享

上图中各区域的颜色代表了相应的角色。活的对象被从一个区域转移(复制和移动)到另外一个区域。各个区被设计为并行收集,并且不会停止所有应用线程。

在图中展示的区域被分配为Eden, survivor和old generation 区。另外,还有第4种类型的区域被成为巨无霸区域。这些巨无霸区域被设计用来保存超过标准区50%或更大的对象。它们被存储在一款连续的区域。最后一种类型的区域是堆中未使用的区域。

3、G1中的年青代

堆大约被划分为2000个区域。最小的堆为1Mb,最大的堆为32Mb。蓝色的区域保存年老代对象,绿色的区域保存年青代的对象。

技术分享

注意G1中的区不需要像老的垃圾收集器一样是连续的。

4、G1的的ygc

活的对象被转移到一个或多个survivors区中。如果对象的收集次数达到阀值,对象会被晋升的年老代。

技术分享

这会发生一次STW暂停。Eden 大小和 survivor的大小会被计算为下次ygc的时候使用。计数信息会保持用来帮助计算Eden和survivor的大小。像暂停时间这类的的事情也会被考虑。

这个方法使重新设置区的大小更加容易,让其更大或更小来满足需求。

5、G1的一次年青代GC后

存活的对象已经被转移到survivor区活着年老代了。

技术分享

最近晋升的对象用深蓝色展示。Survivior区域用绿色展示。

总之,年青代的G1可以描述如下:

堆是被分成许多区域的单独内存空间。

年青代的内存是由不连续的区组成的。使得当需要变更这些区的大小时更加容易。

年青代的垃圾回收活ygc,是STW 事件。所有的应用线程被停止。

ygc由多线程并行执行。

存活的对象被转移到新的survivor活年老代。


年老代用G1进行垃圾收集

同CMS 收集器一样,G1收集器也被设计为一款在年老代的低暂停的垃圾收集器。下表描述了年老代的G1来及收集情况。

G1收集阶段 - 并发标记周期阶段

G1收集器在堆中的年老代中进行如下阶段的垃圾收集。注意有一些阶段也是年青代收集的一部分。

阶段描述

(1)初始标记(STW)

这会发生STW事件。使用G1,这是一次正常的年轻代GC的责任。标记这些可能引用到年老代对象的survivor区域(根区域)

(2)扫描根区域

扫描有引用年老代的survivor区域。这个阶段应用程序可以继续运行。这个阶段必须在一次ygc发生前完成

(3)并发标记

找到整个堆存活的对象。这个阶段应用程序是正在运行的。这个阶段会被年青代的垃圾收集中断。

(4)再标记(STW) 

在堆中标记完成存活的对象。使用一种名为在开始时快照(SATB)的算法,这个算法比用CMS收集器的算法更快。

(5)清理(触发STW 事件并且并发执行)

在存活的对象和完整空闲堆上进行统计(STW)。

清理Remembered Sets(STW)。

重新设置空区域并且返回这些区域的空闲列表(并发执行)。

(6)拷贝(触发STW Event)

暂停应用转移和复制存活的对象到新的未使用的区域。这些操作在年青代中执行被日志记录为[GC pause (yong)]。或者同时在年青代和年老代执行,日志记录为[GC Pause (mixed)]。


G1的年老代垃圾收集步骤

根据G1的各个阶段定义,让我们看看G1收集器如何和年老代进行交互

6、初始标记阶段

初始标记阶段是年青代垃圾收集的责任。在日志中的格式如下 (young)(inital-mark)

技术分享

7、并发标记阶段

在再标记阶段如果有空的区域被发现(标记为红×的区域),这些区域会立即被删除。同样,“统计”信息决定了存活对象的计算。

技术分享

8、再标记阶段

空的区域被删除和回收利用。现在计算所有区的活跃度。

技术分享

9、复制/清理阶段

G1选择最低活跃度的区域,这些区域可以被快速的收集。然后这些区域和ygc的同时被收集。这个阶段在日志中用 [GC parse (mixed)]表示。因此年青代和年老代是在相同时间被回收的。

技术分享

10、复制/清理阶段后

被选择收集和压缩的区域用深蓝色和深绿色表示。

技术分享

年老代GC总结

总之,G1对年老代的GC有如下几个关键点

并发表级阶段

活跃度信息实在应用程序运行时并发运行的。

在转移暂停阶段活跃度信息是标识哪个区域会是最好的回收利用区域。

不像CMS有清理阶段。

再标记阶段

使用比在CMS中更加快速的开始快照算法。

完整的区域是会被重新利用的。

复制/清理阶段

年青代和年老代同时被回收。

年老代是基于活跃度选择的。



也学习Java/JVM/GC (三)