+-
Java虚拟机-垃圾回收

原文链接

回收的是什么?
答:运行程序中没有任何指针引用的对象,这个对象就是需要被回收的垃圾

垃圾回收算法

标记阶段

在GC执行垃圾回收之前,首先需要区分出内存中那些是存活的对象,那些是已经死亡的对象。只有被标记为已死亡的对象,GC才会在执行垃圾回收时,释放掉其存活所占用的内存空间。此过程被称为 垃圾标记阶段 当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡 判断对象存活的有两种方式: 引用计数算法可达性分析算法

引用计数算法(Reference Counting)

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

优点:

实现简单,垃圾对象便于辨识 判定效率高,回收没有延迟性

缺点:

需要单独的字段存储计数器,增加了存储空间 每次赋值都需要重新更新计数器,增加了时间开销 无法解决 循环引用

可达性分析算法

解决了 引用计数算法中循环引用的问题,防止内存泄露 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达 使用该算法后,内存中的存活对象都会被根集合直接或间接的连接着,搜索走过的路径称为 引用链 如果没有任何引用链,则不可达,说明对象已死亡,可以标记为垃圾对象

GC Roots包含以下几类元素:

虚拟机栈中引用的对象 本地方法(native方法)中引用的对象 方法区中常量引用的对象 被同步锁 synchronized持有的对象

Java虚拟机内部的引用

基本数据类型对应的Class对象常驻异常对象和系统类加载器 反映Java虚拟机内部情况的JMXBean、 JVMTI中注册的回调、本地代码缓存等

image

finalization机制

提供对象被销毁之前的自定义处理逻辑 当垃圾回收器发现没有引用指向一个对象,总会先调用这个对象的finalize()方法 finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。例如:关闭文件,断开数据库连接

清除阶段

当成功区分出内存中存活对象的和死亡对象后,接下来就会执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的空间为新对象所分配

标记-清除算法(Mark-Sweep)

执行过程:当堆中的内存空间被耗尽的时候,就会停止整个程序(stop the world),然后进行标记和清除工作。

标记:Collector从引用根节点开始遍历,标记所有被引用的对象,并将这些对象记录在Header中。(找出在程序中被引用的对象,并清除其他的)

清除:Collector从堆内存中从头到尾的进行线性遍历,如果发现某个对象在Header中没有标记为可达对象,则将其回收。(将不可达对象存放在空闲列表中,当有新对象进入时,将其位置覆盖)

缺点:

效率不高 在进行GC时,需要停止整个应用程序,导致用户体验差 清理出来的空闲内存是不连续的,产生内存碎片且需要维护一个空闲列表

复制算法(Copying)

执行过程:当A空间同时存在可达和不可达对象时,将可达对象复制到B空间并保证空间的连续,后清除A空间的所以对象

优点:

没有标记清除过程,实现简单 将存在引用关系的对象复制过去后,保证空间的连续性,不会存在碎片

缺点:

需要两倍的内存空间 对于存活对象的数量远大于垃圾对象数量时,此清除算法效率差。

标记-压缩算法(Mark-Compact)

执行过程:

从根节点开始标记存在引用关系的对象 将这些对象压缩到内存的一端并按顺序排放 清除边界外的所以对象

优点:

对于“标记-清除算法”,由于使用了压缩功能,当有新对象分配内存时,JVM只需要持有一个内存的起始地址即可 对于“复制算法”,减少了内存空间的占用

缺点:

对比“复制算法”,效率低 在移动存在引用关系的对象时,需要调整引用的地址 移动过程中,需要全程暂停用户程序(stop the world)

分代收集算法

根据年轻代和老年代的特点使用不同的内存回收算法

年轻代:

针对年轻代空间小,对象生命周期短,收集频繁和存活率低的特点,使用复制算法可以高效完成内存回收,复制算法使用率不高的问题,通过两个survivor的设计可以有效的缓解。

老年代:

老年代是空间大,对象生命周期长,存活率高和回收不频繁,使用标记-清除算法或标记-清除算法和标记-压缩算法结合完成内存空间的回收。

finalize

内存溢出 和内存泄露