jvm垃圾收集机制详解(上)

6 篇文章 0 订阅

在我们学习java之前,经常听到的一个关于java的优点就是,相对于像C语言这种语言,省去了程序员手动回收垃圾的步骤,那么,java虚拟机到底是怎么实现自动垃圾回收机制的呢?

一、如何判断对象需要被回收

什么时候需要回收对象?经常写别的语言的人可能会说,当我们对一个东西使用完成,需要回收这个东西所占用的空间的时候,需要去手动写代码回收,例如在c++种调用对象的析构函数等的操作。对于java来说,回收内存空间的操作就是垃圾回收,也就是GC操作。

1.对象的死亡

GC操作默认状况下是jvm虚拟机自动进行的,它会确定“活着”的对象和“死去”的对象,死去的含义就是不可能再通过任何手段去使用,例如:

Person p = new Person();
p=null;

我们实例化了一个Person类的对象,然后把对象置为null,此时,p和我们刚才实例化的对象的引用关系就断开了,那么这个时候刚才的Person类实例对象就是一个“死去”的对象了,它将面临被回收的命运。

2.如何判断对象已死

2.1 引用计数算法

算法的名字很高大上,但是想法很简单,我们可以给每一个对象都做一个标记,每当有一个地方引用了这个对象的时候,标记数就加1;当没有地方引用这个对象的时候,标记也就为0,此时意味着这个对象应该被回收了。

算法说起来很简单,但是实际使用会发现一个问题,例如:

我们写了一个类,熟悉数据结构的同学会发现这是链表的一个节点,里面定义了这个节点存储的数据和指向下一个节点的对象。

public class Node {
	int data;
	Node next;
}

下面我们来让两个节点互相指向,构成一个循环链表,然后把指向两个节点的变量置为null

public class Test {
	public static void main(String[] args) {
		Node a = new Node();
		Node b = new Node();
		a.next = b;
		b.next = a;
		a=null;
		b=null;
	}
}

如果用图去理解这个代码的话,就是下图这样:
在这里插入图片描述

我们发现,在断开了a,b的连接之后,现在已经没法通过任何方式获取到这两个循环指向的对象了,此时应该回收这两个对象,但是由于这两个对象互相引用的缘故,它们的引用标记都不是0,因此如果使用这种算法的话面临这种情况的时候没法识别是否需要回收。

2.2 可达性分析算法

由于第一种算法存在的问题,在主流的商用程序语言的主流实现中,都是使用可达性分析算法来判断对象是否还活着。我们使用一种称为GC Roots的对象,来检测对象是否可以访问,如果GC Roots和对象之间还有联系,说明对象不需要被回收,如果一个对象和任何的GC Roots之间都没有了联系,则回收这个对象。

GC Roots说的神秘,其实就是我们上图中说的a和b,a和b就是两个GC Roots,当a和b这两个GC Roots与对象之间断开了连接,没有任何其它的的GC Roots与两个对象相连,此时这两个对象将面临被回收的命运。

可以作为GC Roots的对象包括四种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中Native方法引用的对象

其中,刚才我们的a和b就是第一种GC Roots

2.3 引用的分类

谈到引用,你可能觉得自己已经很明白,不就是地址嘛,引用类型变量的意思无非就是一个变量存储了虚拟机中一块区域的内存地址。但是,你可能不知道,引用其实有4种类型:

  1. 强引用

如果我们:

Object obj = new Object();

那么这种引用就是强引用,在强引用存在的时候,对象不会被垃圾收集器回收。

  1. 软引用
    软引用是一些比较“”的引用,也就是比强引用要弱势一些的引用,当jvm发现运行程序自己的内存不够用的时候,会去找有没有软引用连接着的对象,如果有,就会把这些软引用连接的对象进行二次回收;如果没用软引用对象或者回收了软引用对象之后依然没有对象,这个时候才会抛出内存溢出异常。

  2. 弱引用
    弱引用也是用来描述类似软引用的非必需对象的,但是它的强度比软引用更弱,使用弱引用的对象在下次垃圾回收的时候必然被清除,无论内存是否够用。

  3. 虚引用
    虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会影响它的存活时间,也无法通过虚引用来获得一个对象实例,为一个对象设置虚引用关联的唯一目的就是如果这个对象被回收了会给系统发一个通知。

3.对象是否必死无疑

一个对象没有被GC Roots对象关联,就一定要被回收,必死无疑吗?

答案是否定的,哪可以随随便便就判死刑呢?还是要调查清楚走完流程才能判死刑的,要是万一翻案了呢?

要真正给一个对象宣判死刑,要经过至少两次标记过程:

  1. 如果对象被发现不可达,那么对象会被第一次标记并进行一次筛选,如果对象没有重写finalize()方法或者虚拟机已经调用过finalize()方法,那么没有必要再执行finalize()方法;除此以外的情况,对象将被判定为有必要执行finalize()方法,那么将会进行下面的第二步
  2. 如果对象被判定为有必要执行finalize()方法,对象将会被放置在一个队列中,稍后会由一个虚拟机自动建立的低优先级线程去执行这个对象的finalize()方法。虚拟机会触发这个方法,但是并不必须等待这个finalize()方法执行结束,因为如果一个对象的finalize()方法执行缓慢,或者发生了死循环(极端情况),那就可能会导致队列中的其它对象等待时间过长,可能导致整个内存回收系统崩溃。finalize()方法是对象逃脱死刑的最后一次机会,如果对象想挽救自己,只需要在finalize()方法中重新将自己赋给某个变量即可。执行过finalize()方法之后,对象会被第二次标记,第二次标记之后,对象已经是必死无疑了。
    注意,一个对象的finalize()方法只能被调用一次,也就是说,我们在给对象赋值null然后调用GC回收之后,如果再次给对象赋值,然后再次给对象赋值null,然后再次调用GC回收,这时就不会第二次调用finalize()方法了

请尽量避免使用finalize(),这个方法毕竟只是一个c,c++遗留下来的妥协方法,请尽量忘记这个方法并在心中默念三遍:“我是java程序员,我是java程序员,我是java程序员”

下一篇传送门:jvm垃圾收集机制详解(中)

  • 参考书籍:《深入理解Java虚拟机》
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值