GC 算法

http://www.cnblogs.com/kubixuesheng/p/5208647.html
http://www.cnblogs.com/ityouknow/p/5614961.html
http://blog.csdn.net/linsongbin1/article/details/51686158   CMS回收器
heap分为了eden伊甸园,两个幸存代survivor,前三者也叫年轻代,其次是老年代old。
现代虚拟机的实现当中,垃圾搜集的算法主要有三种,分别是标记-清除算法、复制算法、标记-整理算法

标记-清除算法
现代Java的垃圾回收使用的基本的算法思想是标记-清除算法
  标记-清除算法是现代垃圾回收算法的思想基础。将垃圾回收分为两个阶段:标记阶段和清除阶段。
  一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象(从GC ROOT开始标记引用链—— 又叫可达性算法)。因此,未被标记的对象就是未被引用的垃圾对象。
而这种算法的缺点就是容易出现内存碎片。利用率不高。

标记-压缩算法
现代的Java虚拟机都是使用的分代回收的设计,比如在标记-清除算法的基础上做了一些优化的—— 标记-压缩算法, 适合用于存活对象较多的场合,如老年代。
  和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。有效解决内存碎片问题。

复制算法
还有一个算法, 针对新生代的回收,叫复制算法
   和标记-清除算法相比,复制算法是一种相对高效的回收方法,但是 
不适用于存活对象较多的场合如老年代,使用在新生代,
 
原理是
 
将原有的内存空间分为两块, 两块空间完全相同,每次只用一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。同样也没有内存
 
碎片产生。
 
 
 
复制算法的缺点是内存的浪费,因为每次只是使用了一般的空间,  而大多数存活对象都在老年代,故复制算法不用在老年代,老年代是Java堆的空间的担保地区。复制算法主要用在新生代。在垃圾回收的时候,大对象直接从新生代进入了老年代存放,大对象一般不使用复制算法,因为一是太大,复制效率低,二是过多的大对象,会使得小对象复制的时候无地方存放。还有被长期引用的对象也放在了老年代。
 
Java的垃圾回收机制使用的是分代的思想。
 
依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。 根据不同代的特点,选取合适的收集算法。 少量对象存活(新生代,朝生夕死的特性),适合复制算法, 大量对象存活(老年代,生命周期很长,甚至和应用程序存放时间一样),适合标记清理或者标记压缩算法。
 
以上一定注意:Java没有采用引用计数算法!
 
 
 
经过上述总结,想到 所有的算法,需要能够识别一个垃圾对象,那么怎么才能识别呢?
 
因此需要给出一个可触及性的定义
  • 可触及的–从GC ROOT这个根节点对象,沿着引用的链条,可以触及到这个对象,该对象就叫可触及的,也就是之前说的可达性算法的思想。
  • 可复活的–一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象(finalize方法只会调用一次)。
  • 不可触及的–在finalize()后,可能会进入不可触及状态,不可触及的对象不可能复活,就可以回收了。
  • GC准备释放内存的时候,会先调用finalize()。而调用了这个方法不代表对象一定会被回收。因为GC和finalize() 都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。

最古老的收集器——串行收集器

  最古老,最稳定,效率高,但是串行的最大问题就是停顿时间很长!因为串行收集器只使用一个线程去回收,可能会产生较长的停顿现象。我们可以使用参数-XX:+UseSerialGC,设置新生代、老年代使用串行回收,此时新生代使用复制算法,老年代使用标记-压缩算法(标记-压缩算法首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。有效解决内存碎片问题)。

并行收集器(两种并行收集器)

  • 一种是ParNew并行收集器。使用JVM参数设置XX:+UseParNewGC,设置之后,那么新生代就是并行回收,而老年代依然是串行回收,也就是并行回收器不会影响老年代,它是Serial收集器在新生代的并行版本,新生代并行依然使用复制算法,但是是多线程,需要多核支持,我们可以使用JVM参数: XX:ParallelGCThreads 去限制线程的数量。如图:
GC 算法_第1张图片

  注意:新生代的多线程回收不一定快!看在多核还是单核,和具体环境。、

  • 还有一种是Parallel收集器,它类似ParNew,但是更加关注JVM的吞吐量!同样是在新生代复制算法,老年代使用标记压缩算法,可以使用JVM参数XX:+UseParallelGC设置使用Parallel并行收集器+ 老年代串行,或者使用XX:+UseParallelOldGC,使用Parallel并行收集器+ 并行老年代。也就是说,Parallel收集器可以同时让新生代和老年代都并行收集。如图:

GC 算法_第2张图片


很重要的收集器-CMS(并发标记清除收集器Concurrent Mark Sweep)收集器

  顾名思义,它在老年代使用的是标记清除算法,而不是标记压缩算法,也就是说CMS是老年代收集器(新生代使用ParNew),所谓并发标记清除就是CMS与用户线程一起执行。标记-清除算法与标记-压缩相比,并发阶段会降低吞吐量,使用参数-XX:+UseConcMarkSweepGC打开。

   CMS运行过程比较复杂,着重实现标记的过程,可分为:

  • 初始标记,标记GC ROOT 根可以直接关联到的对象(会产生全局停顿),但是初始标记速度快。
  • 并发标记(和用户线程一起),主要的标记过程,标记了系统的全部的对象(不论垃圾不垃圾)。
  • 重新标记,由于并发标记时,用户线程依然运行(可能产生新的对象),因此在正式清理前,再做一次修正,会产生全局停顿
  • 并发清除(和用户线程一起),基于标记结果,直接清理对象。这也是为什么使用标记清除算法的原因,因为清理对象的时候用户线程还能执行!标记压缩算法的压缩过程涉及到内存块移动,这样会有冲突。
  • 并发重置,为下一次GC做准备工作。

 GC 算法_第3张图片

   CMS的特点
  尽可能降低了JVM的停顿时间,但是会影响系统整体吞吐量和性能,比如:
  1. 在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半。
  2. 清理不彻底。因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理。
  3. 因为和用户线程基本上是一起运行的,故不能在空间快满时再清理。

可以使用-XX:CMSInitiatingOccupancyFraction设置触发CMS GC的阈值,设置空间内存占用到多少时,去触发GC,如果不幸内存预留空间不够,就会引起concurrent mode failure。

可以使用-XX:+ UseCMSCompactAtFullCollection, Full GC后,进行一次整理,而整理过程是独占的,会引起停顿时间变长。

可以使用-XX:+CMSFullGCsBeforeCompaction,设置进行几次Full GC后,进行一次碎片整理。
还可以使用-XX:ParallelCMSThreads,设定CMS的线程数量,一般设置为cpu数量,不用太大。
 
   为减轻GC压力,我们需要注意些什么?

   从三个方面考虑:

  • 软件如何设计架构
  • 代码如何写
  • 堆空间如何分配

你可能感兴趣的:(GC 算法)