java四种引用类型
从jdk1.2以来,java把对象的引用定义为四种级别,从而使程序可以更加灵活地控制对象的生命周期。四种引用类型按照由强到弱的顺序分别为:强引用、软引用、弱引用、虚引用。
强引用
public static void main(String[] args) throws IOException {
M m = new M();
m = null;
System.gc();
System.in.read();
}
m就是持有堆内M对象的一个强引用,也是我们最常见的一种引用形式,属于不可回收资源,垃圾回收期不会主动回收它。当内存空间不足的时候,jvm会抛出OutOfMemoryError错误,也不会主动释放强引用对象。
如果想释放强引用对象,可以显示的把引用复制为null,这样jvm就会在合适的时间释放掉强引用对象。
软引用
SoftReference m = new SoftReference(new byte[1024 * 1024 * 10]);
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] b = new byte[1024 * 1024 * 15];
System.out.println(m.get() );
如果内存空间足够,垃圾回收器不会回收软引用对象,如果内存空间不足,垃圾回收器一定会回收软引用的对象。
弱引用
public static void main(String[] args) {
WeakReference m = new WeakReference<>(new M());
System.out.println(m.get()); // 返回对象
System.gc();
System.out.println(m.get()); // 返回null
}
弱引用有更短的生命周期,垃圾回收器只要扫描到弱引用对象,就会清除弱引用对象。
虚引用
private static final List
虚引用不影响对象的生命周期。虚引用需要和引用队列相关,当垃圾回收器准备回收一个对象,发现它还有虚引用,就会把虚引用加入到与之关联的引用队列中。
ThreadLocal与弱引用
ThreadLocal是什么
ThreadLocal是java.lang
包中实现相同线程数据共享、不同线程数据隔离的工具。
static final ThreadLocal tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+tl.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set("hello");
}).start();
}
ThreadLocal和锁之间的关系:锁解决的是线程之间数据共享的问题,ThreadLocal出现的场景是线程之间没有数据共享,只涉及到同一个线程不同位置使用数据的问题。
ThreadLocal实现原理
对象实例(对应value)和ThreadLocal对象实例(对应key)是由线程Thread来维护的。对象实例和ThreadLocal实例的映射关系存放在Thread对象中的一个map属性中,这个map的类型为ThreadLocalMap。
由于ThreadLocalMap没有对外暴露方法,想要修改或者获取map中的值只能通过ThreadLocal的api来完成。如果Threadlocal对象销毁了,那么就没有办法获取到map中的值了。
// ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 可以看到map是Thread对象的一个属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建map,并赋值给Thread对象的一个属性
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
所以涉及的值存储的地方都是由ThreadLocalMap来完成的,ThreadLocalMap维护了ThreadLocal对象和实例对象的一个映射关系。
// ThreadLocalMap的set方法
private void set(ThreadLocal> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap中使用Entry[]
数组来维护键值对的映射关系,其中key为ThreadLocal对象,value为实例对象value。并且这里的key使用了弱引用,如果对应的ThreadLocal对象的强引用不存在了,这里面的ThreadLocal对象也会被清理掉。
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
这样设计的目的主要是:如果ThreadLocal对象已经不在被强引用了,那么已经没有办法再访问到map中的值了,因为通过Thread对象的属性访问map,然后map再访问value的路径走不通(map的方法不对外暴露)。这个时候可以认为key已经没有用了。
那么为什么不把value也设置为弱引用呢?因为不清楚这个value除了map的引用是否还存在其他的引用。如果是弱引用,当其他地方value的强引用被清理掉之后,gc时就把value(只有弱引用)直接干掉了。Threadlocal对象作为的key还存在,而value却不存在了,显然是不合适的。
ThreadLocal与内存泄露
当把ThreadLocal对象的强引用设置为null之后,就只剩下TheadLocalMap的key为弱引用类型,那么在下次gc的时候会回收掉ThreadLocal对象占用的空间,key就变成null了,而value还存在没有被回收。而且由于map是Thread对象的一个属性,只要Thread对象不销毁,那么value的强引用就一直存在,这样就导致了内存泄露。如果Thread对象过一段时间就销毁,那么影响不会特别大,如果是在线程池场景下,Thread对象一直不销毁,就会导致value一直存在。
所以当线程中的某个ThreadLocal对象使用完了,需要立即调用remove方法,删除Entry对象。