从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。java的引用有强软弱虚四种类型,其中弱引用类型和ThreadLocal密切相关。ThreadLocal:线程本地变变量。以下会对这几个概念原理和使用场景进行分析。
强引用
强引用类型是普通的引用,是最常用的。比如:Object o = new Object()中引用o就是强引用类型。当一个对象具有强引用的时候,GC是不会对其回收的,就算内存不够用,宁可抛出OutOfMemoryError错误也不会回收它。只有这个这个对象不被引用的时候才会被回收。
public class ThreadLocal01 {
public static void main(String[] args) throws IOException {
M m = new M();
System.gc();
System.out.println("有引用时执行gc后 M对象 :" + m);
m = null;
System.gc();
System.out.println("没有引用的时候执行gc后 M对象:" + m);
System.in.read();
}
}
public class M {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize ....被回收");
super.finalize();
}
}

软引用
软引用的创建需要借助jdk中java.lang.ref.SoftReference这个类来创建。也就是说,我们的变量是先引用到SoftReference这个对象,SofReference这个对象再去引用我们想要设置为软引用的对象。
当堆内存够用时,被软引用指向的对象不会被GC回收。
当堆内存不够用时,被软引用指向的对象自动的被GC回收。
例如:
/**
* 运行时 把堆大小设置为30M -Xmx30M
*/
public class Reference01 {
public static void main(String[] args) throws InterruptedException {
// 10M
SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]);
System.out.println(m.get());
System.gc();
TimeUnit.SECONDS.sleep(2);
System.out.println(m.get());
// 又申请15M
byte[] b = new byte[1024 * 1024 * 15];
System.out.println(m.get());
}
}

运行时分配了30M的堆内存,可以看到当内存不够用时,即使SoftReference中指向的对象被引用也会被收回。
应用场景:JVM本地缓存可以使用。
弱引用
弱引用的创建方式与软引用类似,需要借助于jdk中java.lang.ref.WeakReference类去创建。
只有弱引用的对象不管内存够不够用,只要有GC都会被回收。
例子:
public class Reference02 {
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<M>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
}
}
结果:
弱引用的一个经典用法就是ThreadLocal类。下面会着重分析一下ThreadLocal类。
ThreadLocal
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
三个重要的方法:
- public T get()
- public void set(T value);
- public void remove();
public static final ThreadLocal<Object> th = new ThreadLocal<>();
public static void main(String[] args) throws IOException {
th.set(new Object());
Object o = th.get();
th.remove();
}
实现原理源码分析
- 首先看一下Thread类
public class Thread implements Runnable {
......(其他源码)
/*
* 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
* 主要用于父子线程间ThreadLocal变量的传递
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......(其他源码)
}
Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中threadLocals里面存放的变量只对该线程可见,* inheritableThreadLocals* 主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap,不仅对该线程自己可见,其子线程也可见。
inheritableThreadLocal对子线程可见的实现:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
不过本文重点所关注的是ThreadLocal和 ThreadLocal.ThreadLocalMap。

在Thread中threaLocals属性只在ThreadLocal被使用;而这两处代码分别是ThreadLocal类中的ThreadLocalMap getMap(Thread t)和void createMap(Thread t, T firstValue)方法调用。
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
* 返回Thread中的threadLocals
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
* 给Thread中的threadLocals设置初始对象
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- public void set(T value)
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 设置当前线程中的ThreadLocalMap的值,其中key是当前ThreadLocal对象
if (map != null)
map.set(this, value);
else
// map不存在则创建一个
createMap(t, value);
}
ThreadLocalMap map对象中的key是当前ThreadLocal实例,而不是当前线程。
- public T get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 不存在则返回默认值,ThreadLocal默认值是null
return setInitialValue();
}
- public void remove()
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
可以看到这三个方法最终对数据的操作都是ThreadLocalMap类中的Entry数组。ThreadLocalMap是ThreadLocal的静态内部类,Entry是ThreadLocalMap的静态内部类;可以理解为ThreadLocal是ThreadLocalMap的封装,传递了变量值。
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;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
.......其他代码
}
所以最终ThreadLocal和Thread的关系应该是这样子的:
ThreadLocal与弱引用
在ThreadLocalMap中Enty继承了WeakReference类;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Enty的构造函数调用了super(k)方法,那么这时此时Enty的key通过弱引用指向了ThreadLocal对象;所以当ThreadLocal对象只存在弱引用的时候,遇到垃圾回收的时候就会被清理;但是value是强引用,不会被清理掉,这样就会出现key为bull的value。
在ThreadLocalMap实现的时候已经考虑到了这种情况在调用set(),get(),remove()方法的时候,就会清理掉key为null的记录。如果说会出现内存泄漏,那么久只有在出现了key为null的记录后,没有手动调用remove()方法,之后也不再调用 get()、set()方法的情况下。
所以建议收回自定义的ThreadLocal对象,特别是在线程池的情况下,线程会被重复使用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。 尽量在代理中使用try-finally块进行回收:
objectThreadLocal.set(userInfo);
try {
// ...
}
finally {
objectThreadLocal.remove();
}
ThreadLocal 使用场景
- 存储用户Session
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
- 解决线程安全问题:SimpleDateFormat是非线程安全的
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return format1.get().format(date);
}
}
- 数据库连接session
等等
虚引用
虚引用是一种十分特殊的引用,它主要用在堆外内存的管理,虚引用可以指向jvm堆中的对象,但是没有实际的意义。
public class Reference3 {
private static final List<Object> LIST = new LinkedList<>();
public static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> p = new PhantomReference<>(new M(), QUEUE);
new Thread(() -> {
while (true){
// 不停的加内存使其产生GC
LIST.add(new byte[1024 * 1024]);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.get());
}
}).start();
new Thread(() -> {
while (true){
Reference<? extends M> poll = QUEUE.poll();
// 存放管理对外内存对象的QUEUE,如果有管理对外内存的对象被回收,那么该对象会被放到这个队列里
if(poll != null){
System.out.println("---虚引用被jvm回收了");
}
}
}).start();
}
}

虚引用只要是用来管理对外内存的;由于对外内存不归JVM的GC管理,所以当jvm中的对象使用了堆外内存后,要主动的去释放掉。p引用指向一个PhantomReference对象,PhantomReference对象又指向了堆外内存;当GC对PhantomReference对象被回收的时候,并不能同时回收堆外内存;所以就用一个QUEUE队列来存放被GC回收掉的含有堆外内存引用的对象,然后由JVM调用对应的方法释放堆外内存。