Java虚拟机(JVM) | 第二篇:垃圾回收中判断可触及性

一. 简介

      垃圾回收(Garbage Collection,简称GC),是Java体系最重要的组成成分之一。GC中的垃圾,特指存在于内存中的、不会再被使用的对象,而“回收”,也相当于把垃圾桶“倒掉”,这样内存空间里就会有空闲的区域被腾出来。如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占用的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。如果大量不会被使用的对象一直占着空间不放,需要内存空间时,就无法使用这些被垃圾对象占用的内存,从而有可能导致内存溢出。因此,对内存空间的管理来说,识别和清理垃圾对象是至关重要的。

      垃圾回收的基本思想是考察每一个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说, 此对象需要被回收。但事实上,一个无法触及的对象有可能在某一个条件下“复活”自己,如果这样,那么对它的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全地回收对象。

      简单来说,可触及性可以包含以下3种状态:

      ①可触及的:从根节点开始,可以到达这个对象。

      ②可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活。

      ③不可触及的:对象的finalize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。

 

二. 对象的复活

      在前面说到,对象有可能在finalize()函数中复活自己,看下面例子:

public class CanReliveObj {
	public static CanReliveObj obj;

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		obj = this;
	}

	@Override
	public String toString() {
		return "I am CanReliveObj";
	}

	public static void main(String[] args) throws InterruptedException {
		obj = new CanReliveObj();
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if (obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}
		System.out.println("第二次gc");
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if (obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}
	}
}

      上面程序运行结果如下:

CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null

      可以看到,在第一次执行System.gc()前,虽然执行了obj=null清除对象的引用,但在调用finalize()函数时,对象的this引用依然会被传入方法内部,如果引用外泄,对象obj就可以复活,此时,对象又变为可触及状态。而finalize()函数只会被调用一次,因此,第二次清楚对象时,对象就再无机会复活,因此就会被回收。

 

三. 引用和可触及性的强度

      在Java中提供了4个级别的引用:强引用、软引用、弱引用和虚引用。强引用就是程序中一般使用的引用类型,强引用的对象时可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象时软可触及、弱可触及和虚可触及的,在一定条件下,都是可以被回收的。

 

3.1 强引用 -- 不被回收

      给出一个强引用的例子:

StringBuffer str = new StringBuffer("Hello world");
StringBuffer str1 = str;

      假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例将被分在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer实例的强引用。而赋值语句使得str所指向的对象也被str1所指向,此时,该StringBuffer实例就有两个引用,这两个引用都是强引用。

      强引用具备以下特点:

      ①强引用可以直接访问目标对象。

      ②强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常也不会回收强引用所指向对象。

      ③强引用可能导致内存泄漏

 

3.2 软引用 -- 可被回收的引用

      软引用是比强引用弱一点的引用类型。一个对象只持有软引用,那么当堆空间不足时,就会被回收。GC未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出

      每一个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列。通过这个引用队列,可以跟踪对象的回收情况。

 

3.3 弱引用 -- 发现即回收

      弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此并不一定能很快地发现持有弱引用的对象,在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中。

      软引用、弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出,而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。

 

3.4 虚引用 -- 对象回收跟踪

      虚引用是所有引用类型中最弱的一个。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程

      当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后将这个虚引用加入引用队列,以通知应用程序对象的回收情况。由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值