垃圾收集器
图示为JDK1.7 Update14之后的HotSpot虚拟机所使用的所有垃圾收集器。
如果两个收集器存在连线,那么说明这两个收集器可以搭配使用。收集器所处的区域说明它是新生代收集器还是老年代收集器。
Serial收集器
主要看图↑ 上图演示了Serial/Serial Old收集器运行示意图。
Serial收集器是一个单线程收集器,该收集器在进行垃圾收集的同时,必须暂停(Stop the World!)其它工作线程(这就好比你妈妈在打扫房间的同时,肯定也会让你乖乖待在一边,如果她一边打扫,你一边扔纸屑,这房间怎么打扫呢?)。
相比其他新生代收集器,Serial更加简单高效(单线程和多线程相比较没有了线程交互的开销),所以到现在它依然是Client模式下的默认新生代收集器。
ParNew收集器
上图演示了ParNew收集器运行示意图。
ParNew收集器实际上就是Serial收集器的多线程版本,其他和Serial收集器没有本质的差距,如果非要找出不同点,emmm,它是运行在Server模式下的虚拟机中首选的新生代收集器。
parNew收集器在单CPU环境下不一定会比Serial有更出色的表现,但随着CPU核数的增加,它的优势就会显现出来。
Parallel Scavenge收集器
在介绍Parallel Scavenge收集器之前,笔者先为读者定义一个基本概念:吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间),举个例子,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那么吞吐量就是99%。
Parallel Scavenge收集器相比较ParNew收集器最大的区别在于关注点不同,它所关注的是吞吐量而不是Stop the World的停顿时间。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高响应比则可以高效率的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多的交互任务。
主要看图↑ 上图演示了Serial/Serial Old收集器运行示意图。
Serial Old收集器是Serial收集器的老年代版本,这个收集器主要使用在Client模式下,如果是用在Server模式下,分别可以和JDK1.5之前的版本中的Parallel Scavenge收集器搭配使用或者作为CMS收集器的后备预案。
Parallel Old收集器
上图演示了Parallel Scavenge/Parallel Old收集器运行示意图
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,它和Parallel Scavenge收集器的配合可以在整体应用上获得最大吞吐量的效果......
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scanvenge和Parallel Old收集器。
CMS收集器
CMS( Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它主要使用在互联网网站和B/S系统的服务器上。
CMS收集器是基于标记-清除算法实现的,它运作主要包括四个过程
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
初始标记和重新标记这两个步骤仍然需要”Stop the World"
初始标记
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
并发标记
并发标记阶段就是进行GC Roots Tracing的过程。
重新标记
重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
CMS收集器的缺点:
- CMS收集器对CPU资源非常敏感
- CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure"
- CMS采用标记-清除算法进行垃圾回收,算法自身的缺点会导致CMS产生大量的空间碎片产生
G1收集器
上图为G1(Garbage First)收集器的运行示意图
G1是一款面向服务端应用的垃圾收集器。
G1收集器相比其他的垃圾收集器主要有以下优点:
- 并行与并发,G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop The World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
- 分代收集,与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获得更好的收集效率。
- 空间整合,与CMS的标记-清除算法不同,G1从整体来看是基于标记整理算法实现的收集器,从局部(两个Region之间)上来看是基于复制算法实现的。但无论如何,这两种算法都意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
- 可预测的停顿,这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时JAVA(RTSJ)的垃圾收集器的特征了。
补充:使用G1收集器时,JAVA堆的内存布局与其它收集器有很大的区别,它将整个JAVA堆划分为大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
内存分配与回收策略
对象的内存分配,往大方向上讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。
- 对象优先在Eden分配,大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
- 大对象直接进入老年代,大对象(需要大量连续内存空间的JAVA对象,最典型的大对象就是那种很长的字符串以及数组)对虚拟机来说就是一个坏消息,尤其是朝生夕灭的大对象,在写程序的时候要避免使用大对象。
- 长期存活的对象将进入老年代
虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,当被移动到Survivo空间中,并且对象年龄设为1,对象在Survivor区中每”熬过“一次Minor GC,年龄就增减1岁,当它的年龄增加到一定程度(默认15岁),将会被晋升到老年代中。对象晋升老年代的阈值可以通过虚拟机参数来设置。
- 动态对象年龄判定。
- 空间分配担保,内存空间的分配担保就好比我们去银行贷款,如果我们信誉良好,在98%的情况下都能按时偿还,于是银行可能会默认我们下一次也能按时按量的偿还贷款,只需要有一个人能保证我不能还款时,可以从他的账户里面扣钱,那银行就认为我没有风险了。
补充
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但并不一定是并行的,可能会交替执行),用户程序在继续执行,而垃圾收集程序运行在另一个CPU上。
- 新生代(Minor GC):指发生在新生代的垃圾收集动作。Minor GC非常频繁,一般回收速度也比较快。
- 老年代(Major GC/Full GC):指发生在老年代的GC,出现Major GC,经常会伴随至少一次的Minor GC。Major GC的速度一般回比Minor GC慢10倍以上。