分代收集理论并不是一种具体的收集算法,而是当前虚拟机垃圾收集的一种模式,因为堆内存分为新生代和老年代,根据新生代和老年代不同的特性(新生代可能每次都会有大量对象死去,老年代的对象存活率较高)分别采用不同的垃圾收集算法。比如,新生代我们可以选择复制算法,老年代我们可以选择标记-整理或者标记-清除算法。
1.2标记-复制算法它将内存分为大小相同的两块,每次使用其中的一块,当一块内存使用完后,将存活的对象的标记,复制到另一块内存,在将这块内存整个进行清理,这样每次都是对内存的一半进行清理。
缺点:
- 总有一半的内存空缺
该算法分为两个步骤:标记和清除 ,先将内存中存活的对象标记,然后统一回收所有未被标记的(也可以反过来,标记需要回收的,在统一回收所有被标记的)
缺点:
- 如果需要被标记的对象有很多,那么效率就会极低清除过后会产生大量不连续的内存碎片
根据老年代特点出的一种算法,标记过称和标记-清除算法相同,只是清除的时候,是将存活对象移到内存的一端,在对垃圾对象进行清除。这样避免产生大量的内存碎片。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。以下是常见的垃圾收集器
serial(串行),根据名字我们可以知道这种收起器,就是个单线程收集器。但是它的“单线程”不仅仅意味着它在回收垃圾时只会用单线程,更重要的是它在回收垃圾时会暂停其它线程 **“Stop The World”**直到收集完成。
新生代采用复制算法,老年代采用标记-整理算法。
优缺点:
- 缺点:会出现stw,用户体验不好优点:简单而高效(与其他收集器的单线程相比)
Parallel收集器就是serial收集器的多线程版本,Parallell收集器关注的是高吞吐量(高效率的利用cpu)。CMS等垃圾收集器关注点更多是用户线程的停顿时间(用户体验)。吞吐量就是cpu中运行用户代码时间与cpu总消耗时间的比值。
新生代采用复制算法,老年代采用标记-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
ParNew收集器跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
CMS(concurrent mark sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,非常注重用户体验,它是hotspot虚拟机第一款真正意义上的并发收集器,它实现了让垃圾收集线程和用户线程同时运行。
整个过程主要分为四步:
1.初始标记:暂停所有其它线程(STW),并记录下gc roots直接引用的对象,速度很快
2.并发标记:并发标记阶段就是从gc roots直接关联的对象开始遍历整个对象图的过程,这个过程耗时比较长,但是可以和用户线程并发进行。因为用户线程持续运行,可能会导致已经标记过的对象状态发生改变。
3.重新标记:重新标记,主要是为了修正在并发标记过程中用户线程持续运行,导致状态改变的对象。这里主要用到了三色标记里的增量更新算法做重新标记。
4.并发清除:开启用户线程,GC线程同步运行。这个阶段如果有新增对象会被标记为黑色不做任何处理(下面详细解释三色标记)
5.并发重置:重置本次GC过程中标记的数据
优点:并发收集、低停顿
缺点:
对cpu资源敏感(会和服务抢资源)
无法处理浮动垃圾(在并发标记和并发清理阶段产生的垃圾,只能等到下一次GC再清理了)
“标记-清除”算法会产生大量空间碎片,可以通过参数-XX:+UseCMSCompactAtFullCollection让jvm在执行完标记清除后再做整理
执行过程不确定性,会存在上一次垃圾回收还未结束,又触发下一次回收,特别是在并发标记和变并发清理阶段,一边回收,一边运行,也许还没回收完就再触发full gc,也就是“concurrent mode failure”,此时会进入 stop the world ,并用serial old垃圾收集器来回收。
-XX:+UseConcMarkSweepGC:启用cms
-XX:ConcGCThreads:并发的GC线程数
-XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
-XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
-XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
-XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段
-XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
很多优化无非就是让短期存活的对象尽量都留在survivor里,不要进入老年代,这样在minor gc的时候这些对象都会被回收,不会进到老年代从而导致full gc。
三色标记在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。
这里我们引入“三色标记”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:
黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
在并发标记过程中,由于方法运行结束会导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存称之为“浮动垃圾”,浮动垃圾不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收才会被回收。
另外,针对并发标记(并发清理)开始后产生的对象,直接标记为黑色。本轮不会清理,这部分也是浮动垃圾的一部分。
漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。
增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。



