1、java中的四种引用类型
强引用,软引用,弱引用,虚引用
1.1 强引用
强引用是我们常见的引用,创建完对象后,根节点不可达时,被垃圾回收期回收。
1.2 软引用
Java中使用SoftReference<> 作为软引用工具,范型中的引用则为软引用。如下面代码:
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
System.out.println(m.get());
对上述代码,m指向SoftReference对象,SoftReference对象指向byte数组即为软引用,如下图:
软引用有什么用呢?
软引用对象在堆内存不足时,会被垃圾回收。堆内存足够多时不会回收软引用。
因此软引用适合做缓存。
1.3 弱引用
与软引用类似,弱引用使用WeakReference作为弱引用工具,如下代码:
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());
WeakReference对象m的引用 new M();即为弱引用。
弱引用只要有垃圾回收,而且没有强引用指向的时候,就会立即被回收。
弱引用是为了解决某些地方内存泄漏的问题,如:用在ThreadLocal中。
1.4 虚引用
虚引用,使用phantomReference作为引用工具,不同的是构造函数多了一个参数QUEUE,如下代码。
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(),QUEUE);
System.out.println(phantomReference.get());
}
代码中phantomReference.get()是永远拿不到值的,当内存被回收的时候,会把信息发到QUEUE队列中。这样监听者就可以通过队列中的消息判断虚引用被回收了。
那么虚引用的作用是什么呢?
虚引用是用来管理堆外内从。
如NIO引用了零拷贝概念,内从不会拷贝到堆内存,而是在堆外。jvm引用虚引用,如DirectByteBuffer指向堆外内存,就有一个虚引用来管理这块堆外内存,当虚引用被回收时,垃圾回收器判断堆外内存该回收了。
2、上面介绍了java的4中引用类型,只有弱引用没有图示,这要结合ThreadLocal来分析理解。
ThreadLocal是线程的本地存储,通过线程中的ThreadLocalMap来存储当前线程的信息,同时与其他线程隔离开来,互不影响。
而ThreadLocalMap的基本元素Entry就是一个弱引用,我们看看源码。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们知道ThreadLocalMap的key值为ThreadLocal对象,通过上面代码我们发现key是被弱引用对象指向的引用,代码中super(k);
那么这个key就为弱引用,在每次垃圾回收时都会被回收掉。
那么就带出了一个问题,ThreadLocalMap中的key为弱引用,但是value为强引用,当GC发生后,key变成了null,但是value一直存在,并且仍然是根节点可达的对象,无法被垃圾回收,这样就会产生内从泄露了。
如下图:
弱引用只要有垃圾回收,而且没有强引用指向的时候,就会立即被回收。
3、ThreadLocal内存泄露问题解决
JDK源码设计的时候已经考虑过这个问题,ThreadLocal的set和get方法通过ThreadLocalMap做了对旧值的擦除操作。
ThreadLocalMap.set方法:每一次set新值的时候,首先根据threadLocal的hashCode计算在Entry数组中的位置,然后向后环形对entry进行以下操作:
如果entry为null,则直接插入,然后调用cleanSomeSlots方法检测并清除旧entry;
如果entry的k等于当前的threadLocal,代表是同一个threadLocal,直接替换value;
如果当前的entry不为null但k为null,代表上述内存泄漏的情况,调用replaceStaleEntry处理旧entry并set新值;
如果entry不为null,entry的k不为null也不等于当前的threadLocal,代表hash冲突,可能是不同的threadLocal计算出相同的位置,已经有其他threadLocal在这个位置了,然后向后环形重复上述操作;
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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.getEntry方法:每一次get的时候,首先根据threadLocal的hashCode计算在Entry数组中的位置,然后进行以下操作:
如果entry不为null,并且entry中的k等于当前threadLocal,返回entry中存储的value;
如果entry为null,或者entry的k不等于当前threadLocal,查看getEntryAfterMiss方法,对entry数组向后环形遍历;
当entry不为null且entry的k等于当前threadLocal,返回entry里的value;
当entry不为null但entry的k为null,处理当前entry及其value防止内存泄漏;
当entry既不等于当前threadLocal也不为null,说明hash冲突,向后查找下一个entry继续上述操作;
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
问题2:如果两个threadLocal产生hash冲突,按相邻位置set,比如[0]和[1],如果第一个已经垃圾回收,按上述条件会调用expungeStaleEntry,第二个threadLocal处于正常状态是怎么取到值的?
答:关键就在于expungeStaleEntry,如代码所示,首先会将第一个threadLocal所属entry清理掉,然后把相邻threadLocal的entry改到这个位置,即由[1]改到[0],并将[1]上的entry置为null。
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}