java-各种引用介绍

java中有四种类型的引用,关于引用的类在java.lang.ref包下,其类图如下:

java-各种引用介绍_第1张图片
 

各种引用类型介绍

⑴强引用(StrongReference)

    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

 

⑵软引用(SoftReference)

    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

 

⑶弱引用(WeakReference)

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

 

⑷虚引用(PhantomReference)

    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

 

 

 

示列代码如下:

	public void test() {
		//MyDate finalRef = new MyDate();
		MyDate softRef = new MyDate();
		MyDate weakRef = new MyDate();
		MyDate phantomRef = new MyDate();
		
		ReferenceQueue<MyDate> softQueue = new ReferenceQueue<MyDate>();
		ReferenceQueue<MyDate> weakQueue = new ReferenceQueue<MyDate>();
		ReferenceQueue<MyDate> phantomQueue = new ReferenceQueue<MyDate>();
		
		SoftReference<MyDate> soft = new SoftReference<MyDate>(softRef, softQueue);
		WeakReference<MyDate> weak = new WeakReference<MyDate>(weakRef, weakQueue);
		PhantomReference<MyDate> phantom = new PhantomReference<MyDate>(phantomRef, phantomQueue);
		
		softRef = null;
		weakRef = null;
		phantomRef = null;
		
		print(soft);
		print(weak);
		print(phantom);
		System.out.println("phantom.isEnqueued:"+phantom.isEnqueued());
		
		System.gc();
		System.out.println("=============================================================");
		
		print(soft);
		print(weak);
		print(phantom);
		//phantom.enqueue();
		System.out.println("phantom.isEnqueued:"+phantom.isEnqueued());
		
		System.gc();
		System.gc();
		System.gc();
		System.gc();
		System.gc();

		System.out.println("=============================================================");
		print(phantom);
		System.out.println("phantom.isEnqueued:"+phantom.isEnqueued());
	}
	
	public void print(Reference<MyDate> ref) {
		MyDate obj = ref.get();
		System.out.println("ref = "+ref+"\tobj="+obj);
	}

打印结果如下:

ref = java.lang.ref.SoftReference@61de33	obj=Date: 1399972627546
ref = java.lang.ref.WeakReference@14318bb	obj=Date: 1399972627546
ref = java.lang.ref.PhantomReference@ca0b6	obj=null
phantom.isEnqueued:false
=============================================================
ref = java.lang.ref.SoftReference@61de33	obj=Date: 1399972627546
ref = java.lang.ref.WeakReference@14318bb	obj=null
ref = java.lang.ref.PhantomReference@ca0b6	obj=null
obj [Date: 1399972627546] is gc
phantom.isEnqueued:false
obj [Date: 1399972627546] is gc
=============================================================
ref = java.lang.ref.PhantomReference@ca0b6	obj=null
phantom.isEnqueued:true

 多次GC之后,虚引用被加入到引用队列中

 

虚引用会引起OOM

	public void test() {
		Reference<MyRef>[] referent = new PhantomReference[100000];
		ReferenceQueue<MyRef> queue = new ReferenceQueue<MyRef>();

		for (int i = 0; i < referent.length; i++) {
		     referent[i] = new PhantomReference<MyRef>(new MyRef(), queue);// throw
		}
		System.out.println(referent[referent.length-1].get()); 
	}

 设置最大堆内存为2M,最后打印:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at test.ref.TestPhantom.test(TestPhantom.java:21)
	at test.ref.TestPhantom.main(TestPhantom.java:11)

 

 

 

引用类型总结

引用类型 获取引用对象方式 引用对象回收条件 是否会造成OOM
强引用 直接获取 不回收
软引用 通过引用对象的get() 内存满时
弱引用 通过引用对象的get() 垃圾回收时
虚引用 无法获得 不回收

 

 

JDK中的后台线程:

java.lang.ref.Reference$ReferenceHandler      

    当对象被回收时,虚拟机触发这个引用线程,这个线程用来处理各种类型的引用,并将引用加入到引用

   队列中

 

java.lang.ref.Finalizer$FinalizerThread

     当检查到有强引用被加入到队列后,就从队列中取出引用并,之后调用finalize()方法。并将强引用队列的前后引用关系清空

 

引用队列和两个后台线程执行图如下:

java-各种引用介绍_第2张图片
在这个图中, 各种引用被放入到一个队列中,这是一个双向队列,由后台线ReferenceHandler负责处理这个队列,将不同的引用加入到相应的队列中,并将强引用加入到强引用队列中。

此时FinalizerThread检查到有引用加入到队列中了,就将其从最上方的引用队列中删除,然后调用Object#finalize()方法。

 

 

 ReferenceHandler核心逻辑如下:    

	public void run() {
	    for (;;) {
		Reference r;
		synchronized (lock) {
		    if (pending != null) {
			r = pending;
			Reference rn = r.next;
			pending = (rn == r) ? null : rn;
			r.next = r;
		    } else {
			try {
			    lock.wait();
			} catch (InterruptedException x) { }
			continue;
		    }
		}
		// Fast path for cleaners
		if (r instanceof Cleaner) {
		    ((Cleaner)r).clean();
		    continue;
		}

		ReferenceQueue q = r.queue;
		if (q != ReferenceQueue.NULL) q.enqueue(r);
	    }
	}

 

FinalizerThread核心逻辑和引用队列的逻辑:

	public void run() {
	    for (;;) {
		try {
		    Finalizer f = (Finalizer)queue.remove();
		    f.runFinalizer();
		} catch (InterruptedException x) {
		    continue;
		}
	    }
	}

//引用队列会阻塞获取:
    public Reference<? extends T> remove(long timeout)
	throws IllegalArgumentException, InterruptedException
    {
	if (timeout < 0) {
	    throw new IllegalArgumentException("Negative timeout value");
	}
	synchronized (lock) {
	    Reference<? extends T> r = reallyPoll();
	    if (r != null) return r;
	    for (;;) {
		lock.wait(timeout);
		r = reallyPoll();
		if (r != null) return r;
		if (timeout != 0) return null;
	    }
	}
    }

 通过jstack打印出的线程堆栈:

java-各种引用介绍_第3张图片
 

 

 

最后还有一个WeakHashMap,它的键是弱引用类型,值为强引用,当键的引用被回收后,这个KV对就会被删除,WeakHashMap中的Entry,就将Key包装成WeakReference,将加入到弱引用队列中,

每次调用get都会对弱引用队列做检查,如果有数据则将其删除,其实现函数是expungeStaleEntries()

	public void weak() {
		Map<MyRef, Object> weakmap = new WeakHashMap<MyRef, Object>();
		MyRef a = new MyRef();
		MyRef b = new MyRef();
		weakmap.put(a, "aaa");
		weakmap.put(b, "bbb");
		weakmap.remove(a);

		a = null;
		b = null;
		System.gc();
		Iterator<Entry<MyRef, Object>> i = weakmap.entrySet().iterator();
		while (i.hasNext()) {
			Map.Entry<MyRef, Object> en = i.next();
			System.out.println("map:" + en.getKey() + ":" + en.getValue());
		}
	}

 

 

 

参考:

java GC and PhantomReference

Java引用总结

深入探讨 java.lang.ref 包

理解 Java 的 GC 与 幽灵引用

WeakHashMap和HashMap的区别

 

 

 

 

 

 

 

你可能感兴趣的:(java)