JVM-垃圾回收器的相关算法

一、标记阶段:引用计数算法

GC过程:标记阶段、清除阶段(先识别、在清除)

垃圾标记阶段:判断对象是否存活,当一个对象已经不存在任何引用的时候,就是死亡了。

判断对象是否存活一般有2中:引用技术算法和可达性分析算法。

引用计数器算法:对每个对象保存一个整型的引用计数器属性,记录对象被一用的情况。

  • 优点:实现简单,便于标识,效率比较高,回收没有延迟
  • 缺点
    • 增加计数器属性,增加空间开销
    • 因为需要维护计数器,增加时间开销
    • 没办法处理循环引用

因为无法处理循环引用,jdk没有使用。

虽然jvm没有使用,但是其它语言为了高吞吐量,使用引用计数器算法,例如Python,但需要处理循环引用。

二、标记阶段:可达性分析算法(根搜索算法\追踪性垃圾收集)

  • 相对于计数器算法而言,可达性分析算法不仅同样具备实现简单和执行高效的有点,更重要的是该算法可以有效的决绝在引用计数器算法中循环引用的问题,方式内存发生泄漏
  • java、C#都选择了这种算法进行GC

实现思路:

  • GC Roots根集合就是一组必须活跃的引用
  • 可达性分析算法是一根对象集合(GC Roots)为起点,按照从上至下的方式搜索被跟对象集合锁连接的目标对象是否可达
  • 使用可达性分析算法后,内存中的存活对象都会直接或间接的连接着,搜索所走过的路径称为引用链
  • 可达性分析算法中,只有能够被跟对象集合直接或间接连接的对象才是存活的对象

GC Roots包括几类:

  • 虚拟机栈中引用的对象,方法中的参数、局部变量
  • 本地方法栈内JNI引用对象
  • 方法区中类静态属性引用对象,java类的引用类型静态变量
  • 方法区中常量引用的对象,字符串常量池里的引用
  • 所有同步锁synchronized持有的对象
  • JVM内部的应用
  • 反应jvm内部情况的JXMBean、JVMTI中注册的回调、本地代码缓存等
  • 除了固定的GC Roots集合以外,根据用户锁选择的垃圾回收器一级当前回收的内存区域不同,还可以有其他对象临时性的加入,共同完成GC Roots集合。例如新生代的对象在老年代存在引用

堆空间周边的结构,里面的引用都是GC Roots。

因为判断标记的时候必须保持一致性,所有导致了STW。

三、对象finalization机制

  • 对象的finalization机制,就是在对象被GC前调用一个方法
  • 通常用于资源释放,交给垃圾回收器调用,由于可以被重写,所以不可控

对象在jvm中的三中状态,对象可能处理缓刑阶段,一个无法触及的对象可能在某个条件被复活

  • 可触及的:根可达
  • 可复活的:所有引用被释放,但是在finalize() 被复活,复活后的对象不会在调用finalize()
  • 不可触及的:对象的finalize()被调用,并且没有复活

四、MAT于JProfiler的GC Roots溯源

dump快照的导出:

  • jmap 
  • 使用JVisualVm进行导出

MAT:性能调优的工具,基于eclipse

五、清除阶段:标记-清除算法(Mark-Sweep)

当成功标记处对象的状态,GC接下来的任务就是执行垃圾回收,释放无用对象所占的内存空间。

标记-清除算法:常见的基础垃圾回收算法,当堆中的有效空间被好近的时候,就会停止整个程序(STW),然后进行两项工作,第一是标记、第二是清除

执行过程:

  • 标记:使用根可达算法标记处对象是否为"垃圾",标记被引用的对象,在对象头中标记处是否可达
  • 清除:收集器对对内从头到尾进行线性遍历,如果发现某个对象在对象头中没有标记为可达,就对其进行回收

缺点:

  • 效率相对不高,主要因为遍历问题,在标记和清除都是需要
  • 在GC的时候STW,用户线程体验差
  • 清理出来的内存是不连续的,会产生内存碎片,需要维护一个空闲列表

何为清除:清除并不是直接删除,而是记录空间指针,新来的数据直接覆盖老数据

六、清除阶段:复制算法

解决标记-清除算法的效率。将活着的内存空间分为2部分,每次只使用其中一部分,在垃圾回收的时候,将存活的对象复制到另一个违背使用的内存块中,之后清除正在使用的内存块所有的对象,交换两部分内存角色,完成垃圾回收。如果收堆中垃圾很多,那么复制算法需要复制的存活对象梳数量就会很小,所以需要存储对象少效率才高,所以s1和s2才是用复制算法。

 优点:

  • 没有标记和清除过程,效率高
  • 清理出来的空间连续,避免碎片问题

缺点:

  • 需要2倍的内存空间
  • 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象的引用关系,时间和空间开销都不小

七、清除阶段:标记-压缩算法(Mark-Compact)

基于复制算法的特点,存活对象少、垃圾多的前提下,老年代就不适合是用复制算法。所以标记-压缩算法诞生了。

过程:

  • 标记过程和标记-清除算法相同
  • 将所有存活的对象压缩到内存的一端,顺序排放
  • 清除边界之外的所有空间

 优点:

  • 清除了标记-清除算法中,内存不连续的问题,分配内存使用指针碰撞
  • 清除了复制算法中,内存减半的问题

缺点:

  • 效率比标记-整理算法要低
  • 存在移动对象的情况,地址改变,对象的引用也要调整地址
  • 移动过程中,需要STW

八、小结

九、分代收集算法

没有最好的算法、只有最适合的算法。

分代收集算法:集体问题,集体分析。基于这样一个事实,不同对象的生命周期是不同的。因此不同生命周期的对象需要采用不同的收集方式,以便提高回收效率。

年轻代:区域小、对象生命周期短、存活率低,回收频繁,所以使用复制算法

老年代:区域大、对象生命周期长、存活率高,回收不频繁,标记清除和标记压缩一起用

  • Mark阶段的开销与存活对象的数量成正比
  • Sweep阶段的开销与所管理的区域大小成正比
  • Compact阶段的开销与存活对象的数据成正比

以CMS回收期为例,基于Mark-Sweep回收对象,对于碎片整理问题,使用Mark-Compact进行补偿。

十、增量收集算法、分区算法

增量收集算法:解决STW时间比较长的问题,可以让用户线程和垃圾回收线程交替执行。让垃圾回收线程每次回收一小片区域,然后让用户线程再执行,相互交替执行。这样延迟就低了。

缺点:存在线程切换的消耗,是的垃圾回收成本上升,操作系统吞吐量下降。

分区算法:将对空间分割成各小块,根据停顿时长的要求,每次合理的回收若干小空间,而不是整体回收。从而减少了一次GC所产生的的停顿。(降低STW)

猜你喜欢

转载自blog.csdn.net/liming0025/article/details/121690836