1. 垃圾收集算法
1.1 分代收集理论
当前虚拟机的垃圾收集都采用分代收集算法,这种算法,没有什么新的思想,只是根据对象存活周期的不同将内存分成几块。一般将JAVA堆分为新生代、老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法
1.2 标记复制算法
为了解决效率问题,“复制”收集算法出现了。他可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完成之后,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉,这样就是每次内存回收都是对内存区间的一半进行回收
1.3 标记清楚算法
算法分为标记和清除阶段:标记存活的对象,统一回收所有未被标记的对象;也可以反着过来,它是最基础的收集算法,比较简单,但是会带来两个明显的问题
-
- 效率问题(如果需要标记的对象太多,效率不高)
-
- 空间问题(标记后产生大量不连续的碎片)
1.4 标记整理算法
根据老年代的特点标出的一种标记算法,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
2. 主流的垃圾收集器
2.1 Serial收集器(-XX:+UseSerialGc -XX:+UserSerialOldGc)
Serial(串行)收集器是最基本,最古老的垃圾收集器了。看名字就知道这个和收集器是一个单线程收集器。他的单线程不仅仅意味着只会使用一条垃圾收集线程去完成垃圾收集工作,跟重要的是它在进行垃圾收集工作的时候必须暂停其他所有线程的工作STW ,知道线程结束
2.2Parallel Scavenge收集器
Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程垃圾收集外,其余行为(控制参数、收集算法)和Serial收集器类似,默认的收集线程数和cpu核数相同,当然也可以指定线程数,但是一般不推荐修改
Parallel收集器关注点是吞吐量(高效利用cpu) CMS等垃圾收集器的关注点更多是用户线程停顿时间(提高用户体验)
2.3 ParNew收集器(Parallel new 收集器,只能用在年轻代)
ParNew收集器其实跟Parallel收集器很类似,区别在于他可以和CMS收集器配合使用
2.4 CMS收集器(只能用在老年代)
CMS (concurrent Mark Sweep) 收集器是以一种获取最短回收停顿时间为目标的收集器。他非常符合在注重用户体验的应用上使用,他是HotSpot虚拟机第一款真正意义上的并发收集器,它实现了垃圾收集线程和用户线程(基本)同时工作
从名字我们可以看出Mark Sweep可以看出,他是标记清除宣发实现的,运行过程比前面的收集器复杂一点
过程分为5个步骤
-
- 初始标记:暂停其他所有的线程STW,并且记录gc roots直接能引用的对象,速度很快
-
- 并发标记:并发标记就是从GC roots的直接关联对象开始遍历整个对象图的过程,整个过程耗时很长,但是不需要停掉用户线程,可以与垃圾回收线程一起并发运行。因为用户线程继续运行,可能会导致标记过的对象状态发生改变
-
- 重新标记:重现标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记的时间长,远远比并发标记的时间短。主要用到三色标记里的增量更新算法做了重新标记。
-
- 并发清理:开启用户线程,同时gc线程开始对未标记的区域进行清扫,这个阶段如果有新增的对象,会被标记为黑色不做任何处理
-
- 并发重置:重置本次GC过程中的标记数据
其实用CMS收集器STW的时间会变长一点,但是他拆分为了多个小的时间,这样用户几乎感知不到,牺牲了STW的时间,让用户体验变好
从名字我们可以看出他是一块优秀得垃圾收集器,主要有点:并发收集、低停顿。但是有下面几个明显的缺点
- 对CPU资源敏感
- 无法处理浮动垃圾(在并发标记和并发清理产生的垃圾,这种垃圾只能在下一次GC清理)
- 他的回收算法,会有大量的空间碎片,当然你可以通过-XX:+UseCMSCompactAtFullCollection可以让JVM在执行完成之后在做整理
- 执行过程的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情形,特别是并发标记合并发清理阶段会出现,一边回收一边运行,也许没回收完再次出发fullGC,此时会进入STW,用serial old垃圾收集器回收