Java中的四种引用
强引用
强引用是最常见的一种引用类型,在实际开发中,几乎都是强引用类型。
Object obj = new Object();
复制代码
当我们的对象有强引用的时候,即使内存不足,JVM宁愿抛出OOM,也不会把该对象回收掉。如果需要让垃圾回收器回收此对象,需要将强引用关系打破,最简单的方式就是将对象赋值为null。下面写个例子做个简单说明:
创建一个Order对象,为了更好的知晓垃圾回收的情况,这里重写finalize()
方法,因为对象被垃圾回收时都会调用此方法。
/**
* 订单对象
*/
public class Order {
@Override
protected void finalize() throws Throwable {
System.out.println("order对象被垃圾回收");
}
}
复制代码
当对象的强引用关系被打破时,显式调用垃圾回收器
/**
* 强引用类型垃圾回收
*/
public class NormalReferenceTest {
public static void main(String[] args) throws IOException {
Order order = new Order();
// 破坏引用关系
order = null;
// 调用垃圾回收器,进行垃圾回收
System.gc();
System.out.println(order);
// 阻塞Main主线程
System.in.read();
}
}
复制代码
输出结果:
null
order对象被垃圾回收
复制代码
根据输出结果,可知Order对象被垃圾回收掉了。
注意:
在生产环境上,一定不要去重写finalize()
方法,有内存溢出的风险。由于每个对象在进行垃圾回收时都会去调用该对象的finalize()
方法,该方法默认是空的,啥都没做,执行效率是非常快的。但是如果重写后就会影响垃圾回收的效率,假设此时业务上还有大量的对象产生,垃圾回收的效率小于对象产生的效率,时间一长就会内存溢出。
软引用
软引用就是用SoftReference
将引用对象包装一下,通过get()
方法获取包装的对象,使用方式如下:
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
softReference.get();//获取软引用对象
复制代码
这里用一个例子说明软引用的特点
- 设置堆内存大小为20M,JVM参数
-Xmx20M
- 首先通过
SoftReference
包装一个byte数组,大小为10M - 第一次通过
get()
方法获取软引用对象 - 调用垃圾回收器
- 第二次通过
get()
方法获取软引用对象 - 再次new一个byte数组,大小为12M
- 第三次通过
get()
方法回去软引用对象
代码如下:
public class SoftReferenceTest {
public static void main(String[] args) {
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);// 10M
// 获取软引用对象
System.out.println(softReference.get());
// 调用垃圾回收
System.gc();
// 睡眠一下,给与垃圾回收时间
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次获取软引用对象
System.out.println(softReference.get());
// 再new一个字节数组,堆内存不够,会触发垃圾回收,如果内存空间还不够,就会把软引用引用的对象回收掉
byte[] bytes = new byte[1024 * 1024 * 12];// 12M
// 再次获取软引用对象,此时将会获取为null
System.out.println(softReference.get());
}
}
复制代码
运行结果:
[B@677327b6
[B@677327b6
null
复制代码
说明:
第一次获取软引用对象是有值的,接着调用GC,GC垃圾回收后,第二次获取也是有值的,说明垃圾回收时先不会回收软引用对象。随后,继续往内存里面new一个12M的byte数组,显然,我们堆内存是20M,最开始的软引用对象占10M,这里在new 一个,内存是装不下的,所以触发了垃圾回收器回收10M的软引用对象,第三次获取软引用对象时为null,因为都已被垃圾回收器回收掉了。
软引用的特点:
当JVM进行垃圾回收时,如果第一次回收后内存足够,那么不会回收软引用对象;如果第一次回收后内存依旧不够,那么就会回收掉软引用对象。根据这个特点,软引用可以用来做缓存,当系统内存足够时,通过缓存获取值,如果内存不够时,先回收掉缓存,释放一定的内存空间,延迟OOM。
弱引用
弱引用就是通过WeakReference
包装了一下,使用如下:
WeakReference<Order> weakReference = new WeakReference<>(new Order());
// 获取弱引用的值
System.out.println(weakReference.get());
复制代码
弱引用和软引用最大的区别就是无论内存是否足够,弱引用都会被GC回收
/**
* 弱引用
*/
public class WeakReferenceTest {
public static void main(String[] args) {
WeakReference<Order> weakReference = new WeakReference<>(new Order());
// 获取弱引用的值
System.out.println(weakReference.get());
// 垃圾回收
System.gc();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取弱引用的值
System.out.println(weakReference.get());
}
}
复制代码
运行结果:
com.test.soft.Order@677327b6
order对象被垃圾回收
null
复制代码
弱引用在WeakHashMap
,ThreadLocal
中有使用到。
虚引用
Phantom 虚幻的
- 英[ˈfæntəm]
虚引用又叫做幻影引用,它需要配合一个队列来使用,但是我们无法通过虚引用来获取一个对象的真实引用,我们来看下面的代码
ReferenceQueue<Order> referenceQueue = new ReferenceQueue<>();
// 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
PhantomReference<Order> phantomReference = new PhantomReference<>(new Order(), referenceQueue);
System.out.println(phantomReference.get());// 这里输出结果为null
复制代码
上述代码第五行的输出结果居然为null,进入到源码里面会发现
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
}
复制代码
这居然直接返回的null,虚引用不能通过get()方法获取到值,那到底有何存在的意义呢?
我们来看下面的代码:
- 为了演示效果,还是把堆内存大小设置为20M,添加JVM参数
-Xmx20M
- new 一个
ReferenceQueue
队列,创建虚引用,将new Order()
,referenceQueue
作为参数传入 - 开启一个线程,不断的往
list
集合中放入数据,时间长了堆内存会满,触发垃圾回收 - 再开启一个线程,循环读取
ReferenceQueue
队列里面的值
/**
* 虚引用
*/
public class PhantomReferenceTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
ReferenceQueue<Order> referenceQueue = new ReferenceQueue<>();
// 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
PhantomReference<Order> phantomReference = new PhantomReference<>(new Order(), referenceQueue);
System.out.println(phantomReference.get());
new Thread(() -> {
while (true) {
// 不断往list添加数据。内存会满,此时会进行垃圾回收,虚引用就会被放在队列里面
list.add(new byte[1024 * 1024]);
System.out.println(phantomReference.get());
}
}).start();
new Thread(() -> {
while (true){
// 从队列里面读数据
Reference<? extends Order> poll = referenceQueue.poll();
if (poll != null){
System.out.println("虚引用对象被JVM回收" + poll);
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
输出结果:
null
null
null
null
null
null
null
null
null
null
null
null
null
order对象被垃圾回收
null
null
null
null
null
虚引用对象被JVM回收java.lang.ref.PhantomReference@12bc0828
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.test.soft.PhantomReferenceTest.lambda$main$0(PhantomReferenceTest.java:29)
at com.test.soft.PhantomReferenceTest$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
复制代码
虚引用特点:
当垃圾回收器准备回收一个对象,如果发现它还有虚引用,那么就会在回收该对象之前,把这个虚引用加入到与之关联的ReferenceQueue
中。在NIO
中,就用了虚引用来管理堆外内存。