JVM——垃圾回收算法

JVM垃圾回收算法

一.强软弱虚引用

这四种引用从左到右引用强度依次减弱。

  • 强引用(Strong Reference)

程序代码中普遍存在的,我们经常使用的,如

Object obj = new Object();

这类就是强引用。只要强引用还在,垃圾收集器就永远不会回收掉被引用的对象。

  • 软引用(Soft Reference)

用来描述一些还有用但是不必需的对象,通过SoftReference类来实现软引用。

被软引用关联的对象,在系统将要发生内存溢出前将会把这些对象列入回收范围内进行二次回收。

  • 弱引用(Weak Reference)

也用来描述非不必需的对象,通过WeakReference类实现弱引用。

一旦发生下一次GC,被弱引用关联的对象即会被回收。

  • 虚引用(Phantom Reference)

唯一作用就是可以在这个对象被GC回收时收到一个系统通知,通过PhantomReference类实现虚引用。对象是否有虚引用完全不会对其生存时间产生影响,也无法通过虚引用取得对象实例。

二.判断对象是否已“死”

首先明确一个问题:JVM的运行时数据区的哪些部分需要进行GC?

答:方法区和堆。

Java堆中GC经常需要进行对象的回收操作,在它回收堆中的对象实例前,他需要判断当前哪些对象已经”死去“,即没有任何途径使用的对象,这些对象可以被回收。

判断方法:引用计数法可达性分析算法

1.引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器+1,当引用失效时,计数器-1.

当计数器=0时,表示这个对象没有任何引用指向它,即对象“已死”。

  • 作用:记录对象被引用的情况
  • 优点:实现简单,垃圾对象容易辨识;判断效率高,回收没有延迟性。
  • 缺点:需要单独的字段存储计数器,增加了空间开销;每次赋值都要更新计数器的值,增加了时间开销;无法解决相互循环引用问题(比如两个对象互相引用)

2.可达性分析算法

也称根搜索算法引用链法

基本思想:从一系列“GC Roots”对象作为起始点开始,往下进行搜索(搜索走过的路径称为引用链),当一个对象到GC Roots没有任何引用链直接或间接相连的时候,这个对象就是不可用的,即被判定为可回收对象(并非一定会被回收)。

例如,下图中蓝色方框表示的就是可达对象,红色方框就是不可达对象。
在这里插入图片描述

java中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象(各个线程被调用方法中用到的参数,局部变量等)
  • 本地方法栈中JNI引用的对象(native方法引用的对象)
  • 方法区中类静态属性,常量引用的对象(比如字符串常量池中的引用)
  • 所有被同步锁synchronized持有的对象

注意:即使可达性分析算法中不可达的对象,也并非一定会被回收,要真正判断一个对象是否可回收至少还要经历两次标记过程:

第一次标记和筛选

当对象在进行可达性分析后发现对象到GC Roots没有引用链(不可达),就会进行第一次标记和筛选。

筛选条件:判断对象是否有必要执行finalize方法

如果该对象没有重写finalize方法,或finalize方法已经被虚拟机调用过,即没必要执行finalize方法,这个对象即被判为死亡并等待被回收;否则这个对象会进入第二次标记和筛选。

第二次标记和筛选

对象会被放到一个叫F-Queue 的队列中,并由虚拟机自动建立、优先级低的Finalizer线程去执行 队列中该对象的finalize方法

筛选条件:该对象的finalize方法中它有没有重新和引用链上的任何一个对象建立关联

如果有,这个对象就会被移出 “即将回收” 的集合,重新复活;否则判断对象死亡,等待被回收。

可达性分析算法可以解决循环引用的问题:

A a = new A();
B b = new B();
a.instance = b;
b.instance = a;

//假设A,B两个类的声明如下:
class A {
    Object instance = null;
}

class B {
    Object instance = null;
}

此时虽然A和B两个对象的instance成员互相持有对方的引用,但是并没有一个对象有到达任何GC Roots的引用链,并且没有重写finalize方法,所以最终还是会被回收。

三.垃圾回收算法

标记-清除算法

这是最基础的算法,后面的算法都是对它的改进。它分为 标记清除 两个阶段:

1.先标记出所有需要回收的对象

2.完成后对标记对象统一进行回收

标记过程就如上面可达性分析算法所述。

  • 缺点:标记和清除效率都不高;标记清除后会产生大量不连续的内存碎片,导致后面创建比较大的对象时可能提前触发另一次GC。

下图表示了标记-清除算法前后的状态

在这里插入图片描述

复制算法

  • 算法描述:它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了就将还存活的对象全都复制到另一块中,再把已使用过的这块内存全部清理。

    这种收集算法通常用来回收新生代。

  • 优点:简单实用,效率高,不会产生内存碎片

  • 缺点:内存空间使用率低,代价过高;当对象存活率很高时,复制效率较低。

下面是复制算法执行前后的状态:

在这里插入图片描述

标记-整理算法

  • 算法描述:标记过程和标记-清除算法一样,标记完让所有对象向一段移动,然后直接清理掉边段以外的内存。
  • 优点:解决了标记清除算法会产生不连续内存碎片的问题;效率较高。

下面是标记-整理算法执行前后的状态:

在这里插入图片描述

分代收集算法

  • 算法概述:这种算法没有什么新花样,其实就是一个 “因地制宜” 的思想:一般根据对象存活周期的不同将内存划分新生代和老年代,新生代对象存活率较低,采用复制算法;老年代对象存活率较高,没有额外空间来分配担保,采用 标记-清理 或 标记-整理 算法。

  • 空间比例

    新生代:老年代 = 1:2,也就是说新生代占堆容量的1/3,老年代占2/3

    新生代中细分为:Eden,From Survivor和To Survivor区(以下都称作From和To区),Eden:From:To = 8:1:1

这里提个问题:一个Eden区和两个Survivor区的比例为什么是8:1:1 ?

答:Eden区会频繁进行对象的创建和回收,但是GC后其中能存活下来的对象较少,被转移到survivor空间的往往不多。所以设置较大的Eden空间和较小的Survivor空间是合理的,这提高了内存的使用率。

堆内存分布

  • 算法具体描述

    根据堆空间的划分,垃圾收集的类型分为Minor GC和Full GC,前者负责新生代的垃圾回收,后者负责老年代的垃圾回收(通常伴随一次Minor GC)。

    新创建的对象一般会优先分配到新生代的Eden和From区,很长的字符串和大数组等大对象会直接分配到老年代以避免大量的内存复制。

    当经过第一次Minor GC后,Eden和From区中仍然存活的对象会被复制到To区,有两种情况Eden和From中还存活的对象不会被复制到To区,而是直接晋升到老年代。

    • 在To区每经过一轮 Minor GC ,该对象的年龄就+1,当对象年龄到达阈值(15)时晋升
    • To区空间容量到达阈值(1/2)时

    之后Eden和From区会被全部清理,To区和From区角色互换,保证To区总为空。

四.参考资料

《深入理解Java虚拟机》

JVM:这是一份全面 & 详细的 垃圾收集算法(GC) 学习指南

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值