Java的四种引用
1.强引用
最常见的引用类型,即使内存不够也不会回收。
思考:obj1这个强引用自身是否也是对象?占用内存吗?
public class StrongReferenceDemo {
public static void main(String[] args) {
//obj1是一个强引用,指向new出来的实际对象
Object obj1 = new Object();
Object obj2 = obj1;
obj1 = null;
System.gc();
System.out.println(obj2);
}
}
输出结果:java.lang.Object@4d7e1886
2.软引用
内存不够了就回收仅有软引用指向的对象,内存够的时候不用管。
package com.shin.demo05;
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
public static void memoryEnough(){
Object obj1 = new Object();
//创建一个软引用,指向强引用obj所指的对象。
SoftReference<Object> softReference = new SoftReference<Object>(obj1);
System.out.println(obj1);
System.out.println(softReference.get());
obj1 = null;
System.gc();
System.out.println(obj1);
System.out.println(softReference.get());
}
//-Xms5m -Xmx5m 故意制造OOM
public static void memoryNotEnough(){
Object obj1 = new Object();
SoftReference<Object> softReference = new SoftReference<Object>(obj1);
System.out.println(obj1);
System.out.println(softReference.get());
//此时只剩软引用
obj1 = null;
try{
byte[] bytes = new byte[300*1024*1024];
}catch (Throwable e){
e.printStackTrace();
}finally {
System.out.println(obj1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
memoryEnough();
System.out.println("=========================");
memoryNotEnough();
}
}
故意制造OOM,JVM参数-Xms5m -Xmx5m。自动GC的时候把软引用指向的对象回收了。使用softReference.get()可以获得指向的对象。
输出结果:
java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
java.lang.Object@4d7e1886
=============
java.lang.Object@3cd1a2f1
java.lang.Object@3cd1a2f1
java.lang.OutOfMemoryError: Java heap space
at com.shin.demo05.SoftReferenceDemo.memoryNotEnough(SoftReferenceDemo.java:25)
at com.shin.demo05.SoftReferenceDemo.main(SoftReferenceDemo.java:36)
null
null
3.弱引用
仅被弱引用指向的对象在GC的时候一定会被回收,无论内存是否足够。
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void memoryEnough(){
Object obj1 = new Object();
WeakReference<Object> weakReference = new WeakReference<Object>(obj1);
System.out.println(obj1);
System.out.println(weakReference.get());
obj1 = null;
System.gc();
System.out.println(obj1);
System.out.println(weakReference);
System.out.println(weakReference.get());
}
public static void main(String[] args) {
memoryEnough();
}
}
同样是使用weakReference.get()获取弱引用指向的对象。weakReference本身也要占用内存,可以理解为引用类型的对象。
输出结果:
java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
java.lang.ref.WeakReference@3cd1a2f1
null
4.虚引用
仅被虚引用指向的对象也是只要GC就会回收,此外不可以通过虚引用get到指向的对象。
但是可以将虚引用回收的时候入引用队列ReferenceQueue。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceDemo {
public static void main(String[] args) {
Object obj1 = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj1,queue);
System.out.println(obj1);
System.out.println(phantomReference.get());
System.out.println(queue.poll());
System.out.println("===========================");
obj1 = null;
System.gc();
System.out.println(obj1);
System.out.println(phantomReference.get());
System.out.println(queue.poll());
}
}
输出结果:
java.lang.Object@4d7e1886
null
null
===========================
null
null
java.lang.ref.PhantomReference@3cd1a2f1
5.引用队列
软弱虚引用都可以和引用队列配合,在被回收的时候进入引用队列。注意入队的是引用,不是引用指向的对象。
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class ReferenceQueueDemo {
public static void main(String[] args) {
Object obj1 = new Object();
ReferenceQueue queue = new ReferenceQueue();
WeakReference<Object> weakReference = new WeakReference<Object>(obj1,queue);
System.out.println(obj1);
System.out.println(weakReference.get());
System.out.println(queue.poll());
System.out.println("===========================");
//只剩下weak-reference,实际对象会被GC回收
obj1 = null;
System.gc();
System.out.println(obj1);
System.out.println(weakReference.get());
//回收之后入队
System.out.println(queue.poll());
}
}
输出结果:
java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
===========================
null
null
java.lang.ref.WeakReference@3cd1a2f1
弱引用的使用
WeakHashMap的使用
WeakHashMap实现原理和HashMap差不多,区别在于WeakHashMap的key是一个弱引用。
public class WeakHashMapDemo {
public static void notWeak(){
HashMap<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key,value);
System.out.println(map);
key = null;
//显然还在,想要删除的话需要使用remove()方法
System.out.println(map);
System.gc();
//显然还在,HashMap内部都是强引用
System.out.println(map);
}
public static void weak(){
WeakHashMap<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(1);
String value = "WeakHashMap";
map.put(key,value);
System.out.println(map);
key = null;
//不GC的话还在
System.out.println(map);
System.gc();
//WeakHashMap的key是一个弱引用,GC之后key原来指向的对象被回收了
//但是这里看到的是整个键值对都没了,其实用到了引用队列
System.out.println(map);
}
public static void main(String[] args) {
notWeak();
weak();
}
输出结果:
{1=HashMap}
{1=HashMap}
{1=HashMap}
{1=WeakHashMap}
{1=WeakHashMap}
{}
下面一段话摘自《Java核心技术卷1》:
设计 WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的键已经不再使用了,将会出现什么情况呢? 假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键值对无法从映射中删除。为什么垃圾回收器不能够删除它呢? 难道删除无用的对象不是垃圾回收器的工作吗?
遗憾的是,事情没有这样简单。垃圾回收器跟踪活动的对象。只要映射对象是active的,其中的所有桶也是active的,它们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。 或者使用 WeakHashMap完成这件事情。当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。
下面是这种机制的内部运行情况。WeakHashMap使用弱引用(weak references) 保存键。WeakReference对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由 WeakReference引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用,并且已经被收集起来。于是, WeakHashMap将删除对应的条目。
具体看WeakHashMap源码中的expungeStaleEntries()方法。
弱引用的子类MyEntry
如果一个类继承了WeakReference会怎样?
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
//MyEntry继承了WeakReference,那么MyEntry本身就是一个弱引用
//通过父类的构造方法可以指定referent和queue。
public class MyEntry extends WeakReference {
//MyEntry是一个弱引用,自身还携带了一个属性value
private Object value;
//调用这个构造函数会返回一个MyEntry类型的弱引用
//指向的是key所指的实际对象,引用队列是queue
public MyEntry(Object key, Object value, ReferenceQueue queue) {
//调用父类构造函数
super(key,queue);
this.value = value;
}
public static void main(String[] args) {
//1、key是一个强引用
Object key = new Object();
Object value = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
//2、创建了弱引用myEntry,指向强引用key所指的对象
// 当只剩myEntry的时候,GC回收指向的实际对象,然后把myEntry放进queue中
// 注意这里是把谁入队。引用队列入队的一定是引用,而不是引用指向的实际对象。
MyEntry myEntry = new MyEntry(key, value, queue);
System.out.println("key: "+key);
System.out.println("value: "+value);
System.out.println("myEntry指向: "+myEntry.get());
System.out.println("queue: "+queue.poll());
System.out.println("===============================");
key = null;
System.gc();
System.out.println("key: "+key);
System.out.println("value: "+value);
System.out.println("myEntry指向: "+myEntry.get());
System.out.println("queue: "+queue.poll());
}
}
输出结果:
key: java.lang.Object@4d7e1886
value: java.lang.Object@3cd1a2f1
myEntry指向: java.lang.Object@4d7e1886
queue: null
===============================
key: null
value: java.lang.Object@3cd1a2f1
myEntry指向: null
queue: com.shin.demo05.MyEntry@2f0e140b
可以看到myEntry指向的是原本key所指的对象,GC之后被回收了,并且myEntry进入引用队列。