强软弱虚 四种引用类型
1.Java中默认的就是强引用
public class T {
/**
* java垃圾回收时会调用一次且只调用一次
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize执行....");
}
}
*********************************
public class StrongReferenceTest {
public static void main(String[] args) {
T t = new T();
System.gc();
System.out.println(t);
t= null;
System.gc();
System.out.println(t);
}
}
结果:
com.example.reference.T@30946e09
null
finalize执行....
Process finished with exit code 0
强应用,那么如果对象有指向,垃圾回收器将不会回收它。
手动置t为null的话GC回收就会回收该对象
正常情况下,在方法内部有一个强引用,这个引用保存在栈中,而真正的应用内容T保存在堆中,当方法运行结束,就会退出方法栈,引用的变量t就会为null,这个对象就会被回收
但是当为全局变量时,那就不会被回收了。
例如ArrayList的clear()方法,是将每个elementData[i] == null 而非 elementData 对象置为null
2.软引用(SoftReference)
/**
* -Xmx30M
*/
public class SoftReferenceTest {
public static void main(String[] args) {
ReferenceQueue referenceQueue = new ReferenceQueue<>();
SoftReference softReference = new SoftReference<>(new byte[1024*1024*10],referenceQueue);
System.out.println(softReference.get()+"---"+softReference);
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(softReference.get()+"---"+softReference);
byte[] bytes2 = new byte[1024*1024*10];
System.out.println(softReference.get()+"---"+softReference);
Reference extends byte[]> poll = referenceQueue.poll();
System.out.println(poll);
}
}
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存,只要垃圾回收器没有回收它,该对象就可以被程序使用
软引用使用场景,做缓存使用。
软引用可以把一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中
引用队列的作用:
垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用额软引用对象。
对于哪些刚构建的或刚使用过的“较新”软对象会被虚拟机尽可能保留,所以引入引用队列ReferenceQueue的原因
[B@4459eb14---java.lang.ref.SoftReference@5a2e4553
[B@4459eb14---java.lang.ref.SoftReference@5a2e4553
null---java.lang.ref.SoftReference@5a2e4553
java.lang.ref.SoftReference@5a2e4553
通过结果我们可以分析出,
本身softReference连接SoftReference是强引用(第三行,softReference对象有值,)
SoftReference连接byte[] 是软引用,内存不够后,gc回收(第三行,softReference.get() 结果为null)
垃圾回收也只清除软引用的连接,清除不了强引用
3.弱引用(WeakReference)
弱引用与软引用的区别在于,只具有软引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存是否足够,都会回收内存。
public class WeakReferenceTest {
public static void main(String[] args) {
// WeakReference weak = new WeakReference(new T());
WeakReference weak = new WeakReference(new String("abc"));
System.out.println(weak.get() + "---" + weak);
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weak.get() + "---" + weak);
System.out.println("***************************");
System.out.println(weak.get() + "---" + weak);
}
}
abc---java.lang.ref.WeakReference@4459eb14
null---java.lang.ref.WeakReference@4459eb14
***************************
null---java.lang.ref.WeakReference@4459eb14
第一次打印出来,第二次打印之前调用gc,所以会打印null值,
weak指向WeakReference弱引用对象,弱引用对象指向new String()对象,gc会把后者的关系给回收。
实际使用场景ThreadLocal
package com.example.reference;
public class ThreaLocalTest {
private static ThreadLocal threadLocal= new ThreadLocal<>();
public static void main(String[] args) throws Exception {
new Thread(()->{
threadLocal.set(Thread.currentThread().getName()+"-----哈哈");
System.out.println("线程1"+threadLocal.get());
}).start();
new Thread(()->{
System.out.println("线程2"+threadLocal.get());
}).start();
Thread.sleep(500);
System.in.read();
}
}
结果:
线程1Thread-0-----哈哈
线程2null
说明第一个线程的set值干扰不到第二个线程的数据正确性
源码分析:
set方法第一步获取当前线程,然后拿到ThreadLocalMap对象是一个key-value结构,key为this也就是ThreadLocal对象,所以最为关键的是ThreadLocalMap对象如何获取
threadLocalMap对象是Thread类的一个属性,所以说天然不同线程互不干扰
分析createMap发现内部new Entry()对象这个对象继承WeakReference 把key放置其中
ThreadLocal关系图如上:
在线程Thread中 定义一个变量 t1强引用new一个对象ThreadLocal
t1.set()会在Thread内部构建一个ThreadLocalMap对象用于存储信息
存储的信息又是构建成一个Entry对象,而entry对象又是继承弱引用对象
t1通过强引用指向ThreadLocal对象,而Map里面的key又是通过弱引用指向ThreadLocal对象。
如果map里面的引用是强引用,那么就是t1使用结束后,退出栈后但是map里面仍然使用强引用关联导致ThreadLocal这个对象始终无法回收,缓存弱引用的话,只要一gc就回收
假设t1的指向被回收,key指向也被回收,key指向一个null,但是这个map仍然存在,里面的value一直存在,且会一直变大。仍然最后导致内存泄漏。
所以ThreadLocal提供了remove方法在方法使用后进行调用
4.虚引用(PhantomReference)
虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收
虚引用必须和引用队列联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中
虚引用用来管理对外内存,而我们的gc回收的永远是jvm的内存触发不了堆外内存,如果使用虚引用可以以一种别的方式进行内存回收
public class PhantomReferenceTest {
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
PhantomReference reference = new PhantomReference<>(new T(),queue);
System.out.println(reference.get()+"----"+reference);
Reference poll = queue.poll();
System.out.println(poll.get());
}
}
null----java.lang.ref.PhantomReference@4459eb14
Exception in thread "main" java.lang.NullPointerException
at com.example.reference.PhantomReferenceTest.main(PhantomReferenceTest.java:13)