垃圾收集器与内存分配策略(二)

本文介绍了HotSpot的算法实现,包括枚举根节点、安全点和安全区域。还详细阐述了多种垃圾收集器,如Serial、ParNew、Parallel Scavenge等,分析了它们的特点、工作流程及优缺点,如CMS收集器虽并发低停顿,但对CPU敏感、有浮动垃圾和内存碎片问题。

HotSpot的算法实现

枚举根节点

从GD ROOT节点找引用链这个实例来看:可作为GC ROOT的节点主要在全局性的引用(常量或类的属性)与执行上下文(栈针的本地变量表)中。如果逐个检查每个方法区的引用,会消耗较多的时间。

可达性分析对执行时间的敏感还体现在GC停顿上,因为分析工作必须在一个能确保"一致性"的快照中执行,这里的一致性主要是让整个执行系统看起来就像冻结在一个时间点一样,不可以出现在分析过程中对象引用关系仍然在变化中。即使在CMS收集器中,枚举根节点也是必要停顿的。

在HotSpot中,通过一组称为OopMap的数据结构来让虚拟机得知哪些地方存放着对象引用,在类加载完毕之后,HotSpot会把对象内xx偏移量是哪种数据类型,也会在特定的位置记录下栈和寄存器中哪些位置是引用,这样GC在扫描时就可以直接得知这些信息。下图是HotSpot生成的一段String.hashCode()方法的本地代码:在这里插入图片描述

安全点

虚拟机生成的OopMap的点被称为安全点(Safepoint),即程序执行时只有到达安全点才会暂停。

安全点是以"是否具有让程序长时间执行的特征"为标准选定的——"长时间执行"就是指令序列复用,比如方法调用,循环跳转,异常跳转等这些指令。

安全点需要考虑一个问题,即是怎么让GC发生时线程停顿到最近的安全点。有两种方案可以解决:抢先式中断和主动式中断。

抢先式中断是当GC发生时,先把所有的线程中断,如果发现线程不在安全点上,就恢复线程,让他跑到安全点上。

主动式中断是需要中断线程时,设置一个标志,这个标志是和安全点重合的,让所有的线程去轮询这个标志,如果为真线挂起,另外再加上创建对象需要分配内存的地方。下面代码中的test指令是HotSpot生成的轮询指令,当需要暂停线程时,就把0x160100内存页设置为不可读,线程执行到test这里会产生一个自陷异常信号,在预先注册的异常处理器中实现线程等待。
在这里插入图片描述

安全区域

当线程在sleep和blocked状态时,走到安全点中断挂起,对于这种情况就需要安全区域(Safe Region)来解决,因为jvm不会等待线程重新被分配CPU时间。安全区域是指在一段代码中,对象的引用关系不会发生变化,在这个区域任何地方开始GC都是安全的,也可以认为安全区域是扩展的安全点。

当线程执行到安全区域中时,会标识自己进入安全区域,如果这时发生GC,那么GC是不管安全区域中的线程,当线程要离开安全区域时,会检查系统是否完成了根节点枚举(或者GC过程)。如果完成,线程继续执行,否则等待收到可以离开安全区域的信号在离开。

垃圾收集器

收集算法是内存回收的理论,垃圾收集器是内存回收的具体体现。下图是HotSpot虚拟机的收集器:
在这里插入图片描述
如果两种收集器之间存在连线,那么说明这两种收集器可以搭配使用。Young是年轻代,Tenured是老年代。

Serial收集器

Serial收集器是单线程运行的,这里"单线程"的意义是他只能使用一个CPU或者一个线程来完成收集工作,并且在收集工作中,会暂停掉其他的工作线程,直到他收集结束。下图是Serial/Serial Old收集器的工作流程:
在这里插入图片描述

ParNew收集器

ParNew收集器是Serial收集器的多线程版,除了是多线程收集之外,其余的行为包括Seral可用的所有的控制参数,收集算法,Stop The World,对象分配规则等都和Serial一致。下图是ParNew收集器的工作过程:
在这里插入图片描述
ParNew是在新生代收集器中唯一一个可以和CMS收集器一起搭配的,在使用CMS收集器时,新生代收集器就必须在ParNew和Serial中选择一个,不过默认的是ParNew收集器。也可以使用-XX:+UseParNewGC选项来强制指定他。ParNew收集器默认开启的收集线程数和CPU数量是一样的,也可以通过-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

什么是并发?并行?

并行:是指多个垃圾收集线程同时在执行操作,但是用户线程仍处于等待状态。

并发:是指用户线程和垃圾收集线程同时执行(但不一定是并行),用户程序仍在执行,而垃圾收集程序在另一个CPU上。

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,是使用复制收集算法的收集器,并且是并行的收集器。Parallel Scavenge收集器关注点是达到一个可控制的吞吐量,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

停顿时间越短越适合需要与用户交互的程序,良好的体验速度可以提升用户体验,而高吞吐量可以高效率的利用CPU来进行处理,比较适合在后台运行不需要太多交互的任务。

Parallel Scavenge收集提供了两个参数用于精确吞吐量控制,分别是控制垃圾收集的停顿时间的-XX:MaxGCPauseMillis参数和直接设置吞吐量大小的-XX:GCTimeRatio参数。

MaxGCPauseMillis参数允许设置的值是大于0的毫秒数,收集器尽可能的保证垃圾收集时间不超过这个设定数。如果把这个数设置的过小,会导致新生代收集次数过多,吞吐量降低

GCTimeRatio参数允许设置为大于0且小于100的整数值,也就是垃圾收集时间占运行时间的比例,也就是吞吐量的倒数

Parallel Scavenge收集器是一个以"吞吐量优先"的收集器,Parallel Scavenge收集器中的-XX:+UseAdaptiveSizePolicy参数是一个开关参数,当这个参数打开时,会根据当前系统的运行状况提供最合适和停顿时间和吞吐量,这种调节方式是GC自适应的调节策略。使用这种策略只需把基本的内存数据设置好(-Xmx设置最大堆),然后使用MaxGCPauseMillis参数或者GCTimeRatio参数给虚拟机设置一个优化目标。

Serial Old收集器

Serial Old是Serial老年代的版本,使用的是标记-整理算法,它同样是一个单线程收集器。这个收集器主要意义在于给Client模式的虚拟机使用。如果在Server的模式下,主要搭配Parallel Scavenge收集器和作为CMS收集器的后备预案,在并发收集Concurrent Mode Failure时使用。Serial Old的运行流程如下:
在这里插入图片描述

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版。应用在注重吞吐量以及CPU资源敏感的场合,可以考虑Parallel Old+Parallel Scavenge组合。下图是Parallel Old的运行过程:
在这里插入图片描述

CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS是基于"标记-清除"算法来实现的。CMS收集有四个步骤:初始标记,并发标记,重新标记,并发清除。

初始标记和重新标记都需要停顿一下,初始标记仅仅是标记一下GC ROOT能直接关联的对象,并发标记是GC ROOT在查找需要GC的对象的过程,重新标记是是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段停顿的时间较长,但是要比并发标记短很多。

并发标记和并发清除在执行时是和用户线程一起工作的,所以从总体上说,CMS收集器内存回收过程和用户线程是并发执行的。下图可以看到CMS收集器的运行步骤和需要停顿的时间:
在这里插入图片描述
CMS收集器的优点是并发收集,低停顿。但是CMS仍然有一下三个缺点:

1、CMS收集器对CPU资源非常敏感,在并发阶段,由于占用掉一部分CPU的线程,而导致应用程序变慢,总吞吐量会降低。CMS默认启动的线程数量是(CPU数量+3)/4,也就是说,当CPU数量为4个时,收集线程占总线程不少于25%的资源,并且随着CPU数量的增加而下降。当CPU的数量少于4个时,收集线程会占总线程的50%,而导致用户程序的执行速度忽然降低了50%。

2、CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现"Concurrent Mode Failure"失败而导致一次Full GC的产生。CMS收集器清理阶段用户程序仍在运行中,伴随着用户程序的运行产生了新的垃圾,这部分垃圾出现在标记过程中,CMS收集器无法在当次收集中处理掉他们,而只能留到下一次GC进行清除,这部分垃圾被称为浮动垃圾。也是因为垃圾收集阶段用户程序还需要运行,就需要留出足够的内存空间给用户线程使用,因此CMS收集器不会等老年代几乎填满之后在进行收集,需要预留有足够的空间给用户线程使用。在JDK1.5中,CMS收集器在老年代的使用了68%就会激活。如果应用在老年代增长的不是太快,可以通过提高参数-XX:CMSInitiatingOccupancyFraction值来提高触发百分比,通过降低内存回收次数来提高性能。在JDK1.6中,CMS启动阈值提升至了92%,要是CMS运行期间留有的内存空间不满足程序的需要,就会出现异常"Concurrent Mode Failure"失败,这是虚拟机 的后背预案就会启动,临时启用Serial Old收集器来重新进行老年代的收集,这样的话停顿时间变回增长,所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高容易导致大量"Concurrent Mode Failure"失败,从而导致性能下降。
/3、CMS收集器是基于"标记-清除"算法的收集器,也就是说当收集结束之后,会产生大量的内存碎片。当内存碎片过多时,会对大对象的分配带来麻烦,当无法找到足够的空间进行分配对象时,就会提前触发一次Full GC。CMS收集器提供了开关参数-XX:+UseCMSCompactAtFullCollection,这个参数默认就是开启的,用于在CMS将要进行Full GC时开启内存碎片的合并整理过程,这个过程是无法并发的,但是这个参数会导致停顿时间的变长。虚拟机也提供了另一个参数-XX:CMSFullGCsBeforeCompaction,这个参数用来设置执行多少次不压缩的Full GC后,紧跟着一次带压缩的(默认值是0,标识每次Full GC都进入碎片整理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值