【GC算法】jvm垃圾收集算法详解

来源:互联网 发布:淘宝信用卡支付设置 编辑:程序博客网 时间:2024/04/29 12:33

GC算法是作为一个java程序猿所必须了解的东西,不仅在日常生活中有助于我们更深入的理解代码,也是在面试中必定会考的东西。

一:标记-清除算法

    标记-清除算法作为最基础的收集算法,其分为“标记”与“清除”两个阶段,当触发GC时,jvm会暂停所有工作,第一阶段阶段会通过标记算法标记出所有需回收的对象,在之后的第二阶段回收所有被标记的对象。

    市场上主流的标记算法都是可达性分析算法。这个算法的基本思路就是通过一些称之为“GC Roots”的根对象作为起始点,从这些节点往下搜索,搜索路径形成一条引用链(如下图),当一个对象到任意“GC Roots”对象都没有引用链可连通的话,则表明这个对象是不可达的,即这个对象会被标记为可回收的。

可达性分析算法

    上图中的5/6/7对象是没有被任何“GC Roots”引用的,就比如我们一些方法中定义的局部对象变量,在堆中是有对象的,但是在方法执行完后引用已经出栈了,所以没有任何引用去指向他,所以这个对象则是不可达的,将会被标记为可回收

    这里补充下java中可以作为“GC Roots”的对象存在:

  • 虚拟机栈中引用的对象
  • 常量引用的对象
  • 静态属性引用的对象
  • 本地方法栈中JNI引用的对象

    上面已经介绍了标记算法,下面来看清除过程的图解,便于我们理解:

标记-清除算法

    假如当前内存使用状况如图1,红色为未被标记为可回收的对象,黑色为标记为可回收的对象,白色为未使用内存区域,清除算法会回收黑色部分内存,则回收后内存使用状况如图2。这就是标记清除算法。

标记-清除算法的缺点:

  1. 最致命的缺点:效率不高。在标记-清除两个阶段都需要停止程序的运行,且两个步骤效率都不高。试想一下,假如你的网站应用每运行一个小时都要停止10分钟来做垃圾回收,用户体验是何等的不好。
  2. 如上图可以看到,清除后会留下很多小片内存,运行久了都是这种碎片内存的话,在有时我们new一个大对象时,可能会找不到连续的内存空间去分配给对象。

二:复制算法

    为了解决效率问题,“复制”算法应运而生,它将可用内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完时则停止程序,然后将还存活的对象复制到另一块内存中,再把当前这块内存空间全部清理掉。如下图:

复制算法

    上图灰色区域表示当前未使用的一半内存,图2可以看到存活的对象被拷贝到之前未使用的一块内存区域中。

复制算法的优缺点

  • 缺点:牺牲一半内存
  • 优点:如果对象存活率低则运行非常高效,且内存连续性高

三:标记-整理算法

    上面说了复制算法在对象存活率高时则会效率低下,因为每一次复制都会复制大量对象,且会浪费50%的内存,所以有人提出了“标记-整理”算法。标记-整理算法的思路为当内存不足时停止程序,之后触发标记动作,然后将存活的对象全部向一端移动,形成连续的空间,后续空间则可继续使用。如下图:

标记-整理算法

    如上图所有存活对象全部前移,后续空间都为可使用空间。

标记-整理算法优点:

  • 当存活对象多时,效率高于复制算法
  • 不会造成空间浪费

四:分代收集算法

    首先我们来看对象分类:

  1. 新生代对象。新生代对象98%以上都是朝生夕死对象,在一次GC中就会被回收,比如方法中的局部变量等
  2. 年老代对象。年老代往往会存在较长时间,经过很多轮GC都不会被回收,比如数据库连接池对象
  3. 永久代对象。永久代对象几乎不会不会被回收,比如常量池中的对象。

    鉴于对象以及各个算法的优缺点,分代收集算法应运而生,分代收集算法将内存分为新生代、年老代和永久代,我们都知道新生代都是一些“朝生夕死”的对象,其中百分之98%以上的对象都会在一次GC中被回收,所以我们将内存按8:1:1的比例分为一块较大的Eden空间和两块较小的Survivor空间,每次都使用Eden和一块Survivor空间,当空间满时,采用复制算法将当前使用的Eden和Survivor对象复制到另一块Survivor空间中,之后清除当前Eden和当前Survivor空间的内存以完成内存回收。针对老年代和永久代这种存活率很高的区域,则采用标记-整理算法`,以获得更快的效率和连续的内存空间。



总结

  • 新生代都是朝生夕死的对象,则采用“复制算法”,高效且只浪费10%的空间
  • 年老代对象存活率高,则采用“标记-整理算法”,高效且获得连续空间

欢迎关注我的博客:blog.scarlettbai.com

0 0