可触及性(上):总结Java四种强度引用

     

目录

finalize()函数复活对象

示例

引用强度

不会被GC回收的强引用

不可达后被GC回收的弱引用


      上一篇日志简单总结了三种JVM垃圾回收方式,无论是使用引用计数、还是标记压缩清除法等,在进行垃圾回收时,都要先知道哪些对象是需要回收的,哪些是“活的”对象哪些是垃圾对象,判断这些对象的状态,方法是从根节点开始访问每一个对象,如果对象可达,表示该对象目前正在被使用,存在对其的引用,如果某一对象不可达,表示该已经不再被使用了,程序中不存在对其的引用,通常这类对象就是可以被回收的。但是有一种特殊情况就是对象的finalize()函数被调用,该函数只能被调用一次,调用后对象就有可能会复活。

 

finalize()函数复活对象

      在某一对象不存在引用时,JVM垃圾回收会先判断这个对象是否覆盖了这个方法,如果没有覆盖,那么GC会直接将对象回收,如果对象覆盖了finalize()方法,那么会先执行finalize(),执行完毕后,GC再次判断该对象是否可达,存不存在引用,如果此时对象可达,那么对象就复活了,如果对象还是不可达,就可以进行回收,finalize()方法相当于给了对象一次“机会”。判断对象的状态,有三种结果,第一种是对象可触及,也就是说在GC回收对象前遍历过程中可以到达这个对象;第二种是不可触及,不可触及并不是简单的说对象在遍历后不可到达,而是说对象的finalize()函数被调用后,还是没有复活,此时才会进入不可触及状态,这时的对象确定没有任何引用了,可以进行回收。

示例

来看个具体的对象复活的例子:

public class ObjectRelive {
	public static ObjectRelive myObject;
	
	// 重写finalize()
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		myObject = this;
		System.out.println("对象调用finalize()");
	}
	
	public static void main(String[] args) throws InterruptedException {
		myObject = new ObjectRelive();
		myObject = null;
		
		System.out.println("第一次GC:");
		System.gc();
		Thread.sleep(2000);
		if (myObject == null) {
			System.out.println("myObject对象为null.");
		} else {
			System.out.println("myObject对象不为null.");
		}
		
		System.out.println("第二次GC:");
		myObject = null;
		System.gc();
		Thread.sleep(2000);
		if (myObject == null) {
			System.out.println("myObject对象为null.");
		} else {
			System.out.println("myObject对象不为null.");
		}
		
	}

}

      ObjectRelive类覆盖了finalize()方法,然后在主函数中,new一个ObjectRelive对象myObject,第14行将其置为null后,调用System.gc()进行第一次垃圾回收,此时虽然对象的引用被清楚了,但是类中的finalize()对象被覆盖,对象会调用该方法,执行里面的“myObject = this;”后,将当前对象的this传入方法里面,所以对象被复活了,变成了可触及态,按道理来说第一次GC之后myObject对象不为null。之后第26行开始我们进行第二次垃圾回收,还是先把myObject再次置为null,然后调用System.gc(),因为对象的finalize()方法只会被调用一次,所以对象无法再次复活,引用被清空后只能GC回收。

      这里有些地方要注意的是,在我写代码时发现finalize()方法是一个已经被废弃的方法,查了些资料后说该方法在Java程序中并不能保证及时执行,通常在System.gc()后调用该方法,但也不能保证它一定会被执行。

 

引用强度

      编写Java程序的我们虽然不用显式地去释放不需要的对象空间,但是如果我们想要“手动”去控制一些变量对象的生命周期,例如想要一个对象长期驻留的程序中,可以定义成强引用,如果想要JVM去自动管理回收某些对象,可以用弱引用或者软引用,例如对一些缓存数据,并不是特别重要,那么当内存空间不足时,就可以让GC回收这些缓存数据。除了强引用,弱引用和软引用外,还有一种叫虚引用,下面来简单看看这四种强度的引用。

不会被GC回收的强引用

      强引用类型的对象在遍历过程中是可触及的,不会被GC回收,一个简单的例子:

String s1 = new String(“Reference”);

      声明一个String类型的局部变量s1并实例化,这时变量s1会指向实例的堆空间(局部变量分配在栈空间上,实例分配在堆空间中),此时的变量s1对String实例就是强引用。强引用可以让变量s直接访问String实例,且因为强引用的存在,GC不会将它回收,好处是生命周期长,缺点是如果大量的强引用存在,如果内存空间不足,可能会造成内存泄漏。多个局部变量同时指向同一实例也是可以的,如果我们在声明一个String类型的变量s2,并让它也指向同一个实例:

String s1 = s2;

      此时变量s2和s1指向的都是同一个实例,它们存放的地址是相同的,及时s1变量引用被释放掉,由于s2引用的存在,实例也不会被GC回收。

不可达后被GC回收的弱引用

      弱引用类型对象在JVM进行垃圾回收时,只要被发现是不可达状态,就会被立刻回收,哪怕当前内存空间很充足,来看一个例子:

public class WeakRefInstance {
	
	public static class Student {
		public int studentID;
		public String studentName;
		
		public Student(int studentID, String studentName) {
			this.studentID = studentID;
			this.studentName = studentName;
		}
		
		@Override
		public String toString() {
			return "studnetID = " + studentID + ", studentName = " + studentName;
		}
	}
	
	public static void main(String[] args) {
		Student s = new Student(11, "张三");
		WeakReference<Student> studentWeak = new WeakReference<Student>(s);
		s = null;
		System.out.println(studentWeak.get());
		
		System.gc();
		System.out.println(studentWeak.get());
	}

}

      在代码中,第19行首先声明了一个Student变量s并实例化,它是一个强引用,然后下一行构造了一个弱引用队列studentWeak,并获取对象s,之后显式将s置为null,这时Student实例失去了强引用。第22行我们从弱引用队列studentWeak中获取得到Student实例,从输出看是可以得到的,此时的Student实例就存在弱引用,接下来执行System.gc()进行垃圾回收,弱引用Student实例被发现不可达后,会立刻被回收掉:

从最后一句输出可以看到,它的确被GC回收,从弱引用队列中再次获取这个对象时得到的是null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值