了解垃圾收集器之前,先看下他们的算法。
一:垃圾收集算法
1.复制算法
为了解决效率问题,“复制”收集算法。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就降还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就每次的内存回收都是对内存区间的一半进行回收。
2.标记-清除算法
算法分为,标记和清除两个阶段:标记存活的对象,统一回收所有未标记的对象.它是最基础的收集算法,比较简单,但是会有两个明显的问题:
- 效率问题(如果需要标记的对象太多,效率不高)
- 空间问题(如果清除后产生大量不连续的碎片)
3.标记整理算法
根据老年代的特点特出的一种算法,标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
4.分代收集理论
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新思想,只是根据对象存活周期的不同将内存分为几块。将一般Java堆分为新生代和老年代,这样我们可以根据各个年代的特点选择合适的垃圾收集算法。
例如:新生代中每次收集都会有大量的对象死亡,所以可以选择复制算法,只需要付出少了对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们选择“标记-清除”活“标记-整理”算法垃圾收集。
标记-清除或标记整理算法会比 复制算法慢10倍以上。
二:垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
知道目前为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器。我们能做的就是根据具体应用场景选择合适自己的垃圾收集器。
1.Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Serial(串行)收集器是最基本、历史最悠久的收集器。这个收集器就是一个单线程收集器。它的“单线程”的意义不仅仅意味着它只会使用一套垃圾收集收集线程去完成垃圾收集工作,更重要的是它在垃圾收集工作的时候必须要暂停其他所有的工作线程(“Stop The World”),直到它收集结束。
新生代采用复制算法,老年代采用 标记-整理算法。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
2.Parallel Scavenge收集器(-XX:+UseParallelGC ; -XX:+UseParallelOldGC)
Parallel收集器其实是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等)都和Serial收集器类似。默认的收集器线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
Parallel Scavenge收集器关注点是吞吐量。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间。所谓吞吐量就是cpu中用于用户代码的时间与cpu总耗时的比值。
新生代采用复制算法,老年代采用标记-整理算法。
Parallel Old 收集器是Parallel Scavenge收集器的老年代版本。使用多线程和"标记-整理"算法。在注重吞吐量以及Cpu资源的场合,都可以优先考虑Parallel Scavengle收集器和Parallel Old收集器(JKD8默认的新生代和老年代收集器)
3.ParNew收集器(-XX:+UseParaNewGC)
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器配合工作。
4.CMS收集器(-XX:UseConcMarkSweepGC)
CMS(concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤
- 初始标记:暂停所有的其他线程(Stop the world),并记录下gc roots 直接能引用的对象,速度很快。
- 并发标记:并发阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已标记过的对象状态发生改变。
- 重新标记:重新标记阶段就是为了修改并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般比初始阶段的时间稍长,远远比并发标记阶段的时间短。主要用到三色标记里面的增量更新算法做重新标记。
- 并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理。
- 并发重置:重置本次GC过程中的标记数据。
主要优点:并发收集、低停顿。
主要缺点:
- 对CPU资源敏感(会和服务抢资源)
- 无法处理浮动垃圾(在并发标记和并发清理节点又产生垃圾,这种浮动垃圾只能等到下一次GC在清理了)
- 它使用的回收算法"标记-清除"算法会导致收集结束时会有大量空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理。
- 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会初夏你,一边回收,系统一边运行,也许没回收就再次触发Full gc,也就是"concurrent mode failure" ,此时会进入stop the world, 用Serial Old垃圾收集器来回收。
CMS的相关核心参数
- -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的标记阶段的开销,一般CMS的GC耗时80%都在标记阶段
- -XX:+CMSParallellnitialMarkEnabled:表示初始标记的时候多线程执行,缩短stop the world
- -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短stop the world.