摘要
工作中,经常会遇到很多场景有使用线程级别变量的这种情况(线程隔离)!本篇博文主要带大家来认识以下这个神秘ThreadLocal相关的应用知识以及底层原理;
介绍
根据JDK源码注释,该类ThreadLocal主要用于提供线程本地变量。这些变量与普通变量的不同之处在于,每个访问变量的线程都有自己独立初始化的变量副本(通过其*{@code get}或{@code set}方法)。 实例通常是类中希望将状态与线程关联的私有静态字段 (例如,用户ID或事务ID)。
例如,下面的类为每个线程生成本地唯一标识符。线程的id在它第一次调用{@code ThreadId.get()}时被分配,并且在后续调用中保持不变 。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
只要线程处于活动状态且ThreadLocal实例可访问,每个线程都持有对其线程本地变量副本的隐式引用;线程消失后,其线程本地实例的所有副本都将接受垃圾收集(除非存在对这些副本的其他引用)。
主要方法
ThreadLocal主要有一个空的构造器方法、一个受保护的方法和四个public方法:ThreadLocal()、initialValue()、get()、set(T value) 、remove()、withInitial(Supplier<? extends S> supplier)
initialValue()方法
protected T initialValue() {
return null;
}
返回此线程本地变量的当前线程“初始值”。当线程第一次使用{@link#get}方法访问变量时,将调用此方法,除非线程之前调用了{@link#set}方法,在这种情况下,不会为线程调用{@code initialValue}方法。通常,每个线程最多调用一次该方法,但在随后调用{@link#remove}和{@link#get}的情况下,可以再次调用该方法。 <p>这个实现只返回{@code null};如果程序员希望线程本地变量具有除{@code null}}之外的初始值,{@code ThreadLocal}必须被子类化,并且这个方法被重写。 通常,将使用一个匿名内部类。
get()方法
//返回当前线程本地变量副本中的值。 如果变量没有当前线程的值,它首先被初始化为调用{@link #initialValue}方法返回的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//为线程本地变量设置初始值
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//获取线程本地变量表
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
要理解这个get方法,咋们还得看看Thread这个类,其实在Thread类里面声明了一个变量map用于保存所有线程本地变量;
ThreadLocal.ThreadLocalMap threadLocals = null;
它的初始化就是在客户端调用ThreadLocal.get()方法时进行;
注:ThreadLocal中调用创建ThreadLocalMap时,传递了当前ThreadLocal实例的引用this作为key,此处且看下文分析;
set(T value) 方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
将这个线程本地变量的当前线程副本设置为指定的值。 大多数子类将不需要重写这个方法,只依赖{@link #initialValue}方法来设置线程本地变量的值。
remove() 方法
public void remove() {
ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
移除此线程本地变量的当前线程值。 如果这个线程本地变量随后被当前线程{@linkplain #get read}调用,它的值将通过调用它的{@link #initialValue}方法重新初始化,除非它的值在过渡期间被当前线程{@linkplain #set set}调用。 这可能会导致在当前线程中多次调用{@code initialValue}方法。
withInitial(Supplier<? extends S> supplier)方法
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
/**
* An extension of ThreadLocal that obtains its initial value from
* the specified {@code Supplier}.
* ThreadLocal的一个扩展,它从指定的{@code supplier供应商}获取初始值;此类是ThreadLocal的一个静态内部类;
*/
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
创建一个线程本地变量。 变量的初始值是通过调用{@code Supplier}上的{@code get}方法来确定的。 @param <S>线程本地值的类型 @param supplier用于确定初始值的供应商 返回一个新的线程本地变量 如果指定的供应商为空,则抛出NullPointerException
package java.util.function;
/**
* Represents a supplier of results.(表示结果的提供者)
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.(并不要求每次调用供应商时都返回一个新的或不同的结果 )
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #get()}.(这是一个函数式接口,其函数方法是{@link #get()}。 )
*
* @param <T> the type of results supplied by this supplier(该供应商提供的结果类型)
*
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
其实以上看着很复杂,实际就是ThreadLocal提供的一个函数式编程接口实现初始化的方式(Lambda构造方式); 具体用法如下:
import java.util.concurrent.atomic.AtomicInteger;
public class TestThreadlocal {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static ThreadLocal<Integer> local = ThreadLocal.withInitial(()->nextId.getAndIncrement());
public static void main(String[] args) throws InterruptedException {
Runnable r = new TT();
new Thread(r, "thread1").start();
Thread.sleep(2000);
new Thread(r, "thread2").start();
}
private static class TT implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" set local name and get: "+ local.get());
}
}
}
createInheritedMap方法
/**
*工厂方法来创建继承的线程本地变量的映射。 设计为只能从线程构造函数调用。
*
* @param parentMap the map associated with parent thread
* @return a map containing the parent's inheritable bindings
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
*方法childValue是在子类InheritableThreadLocal中明显定义的,但这里是在内部定义的,以便提供createInheritedMap工厂方法,
*而不需要在InheritableThreadLocal中创建映射类的子类。
*这种技术比在方法中嵌入测试实例更可取。
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
内部类知识
ThreadLocal主要有两个内部类:ThreadLocal.SuppliedThreadLocal、ThreadLocal.ThreadLocalMap.
ThreadLocal.SuppliedThreadLocal内部类
该内部类在前面讲withInitial(Supplier<? extends S> supplier)方法的时候已经提到,它是ThreadLocal内部实现的一个自己的子类,该内部类通过构造函数包装一个supplier函数式接口,该接口定义了一个get方法;而在重写initialValue()方法时,调用类传入进来的supplier函数实例的get方法返回;换句话说-它是用于协助ThreadLocal实现Lambda构造方式的一个核心类;具体如下:
protected T initialValue() {
return supplier.get();
}
ThreadLocal.ThreadLocalMap内部类
ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。 没有操作被导出到ThreadLocal类之外。 这个类是包私有的,允许在类Thread中声明字段。 为了帮助处理非常大和长期的使用,散列表条目使用WeakReferences作为键。 但是,由于没有使用引用队列,所以只有在表空间耗尽时才保证删除过时的条目。
该静态内部类中定义Entry这个静态内部类
static class ThreadLocalMap {
/**
* 这个散列映射中的条目扩展WeakReference(弱引用),使用它的主引用字段作为键(始终是ThreadLocal对象)。 注意,空键(即entry.get() == null)意味着该 * 键不再被引用,因此条目可以从表中删除。 这样的条目在下面的代码中称为“陈旧条目”。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap源码中可以看到,每个线程局部变量的数据其实最终以Entry对象存放在ThreadLocalMap的一个Entry[]数组中;
我们在之前的ThreadLocal.get()方法中,已经了解到--在客户端第一次调用get()方法,会以ThreadLocal实例对象作为key,ThreadLocal的initialValue()方法返回结果作为value作为参数初始化一个ThreadLocalMap返回并且设置到当前线程的threadLocals字段上面;
ThreadLocal.ThreadLocalMap构造方法
/**
* The number of entries in the table.(当前数组容器中存放的entry数)
*/
private int size = 0;
/**
* The next size value at which to resize.(要调整大小的下一个大小值,阈值)
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.(将调整大小阈值设置为最坏保持2/3的负载因子)
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 初始化容量大小 -- 必须是2的幂.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, 根据需要调整表的大小.
* table.length MUST 必须是2的幂.
*/
private Entry[] table;
/**
* 构造一个最初包含(firstKey, firstValue)的新map
* ThreadLocalMaps是惰性构造的,所以我们只在至少有一个条目要放入的时候才创建一个该对象
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化容量为16的一个Entry数组作为容器存放数据
table = new Entry[INITIAL_CAPACITY];
//找到对应的hash槽位
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//设置数组该位置的项,key为ThreadLocal实例,value则为ThreadLocal.initialValue()方法返回值
table[i] = new Entry(firstKey, firstValue);
//设置当前数组的真实size
size = 1;
//初始化扩容阈值16*2/3=10
setThreshold(INITIAL_CAPACITY);
}
ThreadLocal 哈希计算分析:
根据源码可以看出,ThreadLocalMap计算hash槽位的方法定义在ThreadLocal中;
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
此处的&是Java中按位与运算符;如果此处不太明白,可以先补充下''位运算''相关知识
/**
* ThreadLocals依赖于附加到每个线程的每个线程的线性探针哈希映射(thread。 threadlocal和inheritablethreadlocal)。 ThreadLocal对象充当键,
* 通过threadLocalHashCode搜索。 这是一种自定义散列代码(仅在ThreadLocalMaps中有用),它可以在相同线程使用连续构造的ThreadLocals的常见情况下消 * 除冲突,而在不常见的情况下保持良好的行为。
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.(下一个要给出的哈希码。 自动更新。 从0开始。 )
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* The difference between successively generated hash codes(连续生成的哈希码之间的差值 ) - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.(返回下一个散列码)
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
从上面的代码可以看出ThreadLocal的每个实例的threadLocalHashCode都不一样,并且只在创建ThreadLocal实例的时候初始化一次;
ThreadLocal.ThreadLocalMap.getEntry方法
该方法用于根据ThreadLocal实例作为key获取线程本地变量map中对应的entry对象,如果根据hash获取到的entry里面的key和当前传进来的key不是同一个,则转到getEntryAfterMiss方法依次从当前定位的槽位往后查找对应的entry, 后续步骤如果遇到已过期的条目就删除该过期槽位的entry项,并检查并处理当前槽位开始往后(环形检测,即如果到了表末端还未遇到空槽位,则会返回到表头继续往后检测)所有entry是否过期,直到遇到一个槽位entry为null为止.
/**
* 获取与key相关联的条目。 这个方法本身只处理快速路径:直接命中存在的key。 否则它会转发到getEntryAfterMiss。
*
* @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);
}
/**
* getEntry方法的版本,用于在直接哈希槽中找不到键时使用。
*
* @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)
//k==null表示,key已经被回收(注:key是保存在弱引用对象entry中的)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* 通过重新散列位于staleSlot和下一个空槽之间的任何可能冲突的条目来删除陈旧的条目。 这还会删除尾随null之前的任何陈旧条目。 参见Knuth,章节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 (重新hash,直到遇到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;
// 与Knuth 6.4算法R不同,我们必须扫描到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;
}
/**
* Increment i modulo len. i对len取模
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
ThreadLocal.ThreadLocalMap.set方法
该方法用于设置线程本地变量的值,其过程中如果遇到弃用的槽位会进行清理,并且当在新的槽位插入后,还会触发清理过期条目动作,并根据size大小判断是否进行扩容
/**
* 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) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//按顺序从i槽位向后面遍历
for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果找到k对应的项,则重新赋值
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();
}
//替换过期的项
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 备份以检查当前运行中先前的陈旧条目。
// 我们一次清理整个线,以避免由于垃圾收集器释放了成组的ref(即每当收集器运行时)而导致的持续的增量重散列。
int slotToExpunge = staleSlot;
// 以 staleSlot 为基础,向前查找到最前面一个弃用的哈希槽,并确立清除开始位置
for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 查找run的键或尾随的空槽,以先出现的为准
// 以 staleSlot 为基础,向后查找已经存在的 ThreadLocal
for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果我们找到了key,那么我们需要将它与陈旧的条目交换,以维护哈希表的顺序。 然后,
//可以将新过时的插槽或在其上面遇到的任何其他过时插槽发送到expungeStaleEntry,以删除或重新散列运行中的所有其他条目。
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
//向前环形遍历
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocal.ThreadLocalMap.remove方法
该方法用于删除一个线程本地变量值,其中也会涉及到清理弃用槽位
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
//clear方法定义在JDK的引用抽象类中
public abstract class Reference<T> {
...
public void clear() {
this.referent = null;
}
...
}
ThreadLocal.ThreadLocalMap.扩容方法
在调用set时,如果在新槽位插入,会检查当清理一些过时的槽位;此时,如果剩下的已经使用的槽位达到了扩容的阈值,就会调用 rehash();并且将容量扩为之前的两倍;因为初始容量为16,所以ThreadLocalMap的容量一直是2的幂
//重新包装和/或重新设置大小的表。 首先扫描整个表,删除过时的条目。 如果这不能充分收缩表的大小,则将表的大小加倍。
private void rehash() {
//删除过时项
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.(扩容为之前的两倍,并且将旧表的值,插入到新表)
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
InheritableThreadLocal
InheritableThreadLocal提供可继承的线程本地变量,它是ThreadLocal的子类;当在变量中维护的每个线程属性(例如,用户ID、事务ID)必须自动传输给创建的任何子线程时,可继承的线程本地变量优先于普通线程本地变量。
定义如下:
/**
* 这个类扩展了<tt>ThreadLocal</tt>来提供从父线程到子线程的值继承:当创建子线程时,子线程接收到父线程有值的所有可继承的线程本地变量的初始值。
* 通常,子进程的值与父进程的值是相同的; 但是,通过重写这个类中的<tt>childValue</tt>方法,子节点的值可以用任意函数去映射父节点的值获得。
*/
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
*为这个可继承的线程本地变量计算子线程的初始值,作为子线程创建时父线程值的函数。 在子线程启动之前,从父线程内部调用此方法。
*此方法仅返回其输入参数,如果需要不同的行为,则应该重写此方法。
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
使用案例:
import java.util.concurrent.atomic.AtomicInteger;
public class TestThreadlocal1 {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static ThreadLocal<Integer> local = ThreadLocal.withInitial(()->nextId.getAndIncrement());
public static void main(String[] args) throws InterruptedException {
Runnable r = new TT();
new Thread(r, "thread1").start();
Thread.sleep(2000);
new Thread(r, "thread2").start();
}
private static class TT implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" set local name and get: "+ local.get());
}
}
}
最后总结:
1、ThreadLocalMap底层数据结果是数组实现,数组的类型为Entry,Entry项继承自WeakReference(JDK弱引用),Map的key是ThreadLocal实例对象(存储在entry继承的弱引用中),并且Entry扩展了一个value字段用于保存值;
2、ThreadLocalMap在解决hash冲突的时候和HashMap不同,它没用使用链表,而是直接以定位到的槽位为初始向后进行探测,如果探测到可用槽位则插入值;同时中间过程伴随着过时entry清理,以及扩容,向前移动等等一系列的操作;
3、ThreadLocal 通过线程独占的方式,也就是隔离的方式,避免了多线程问题;
4、在使用 ThreadLocal 的时候最好手动移除,以避免内存泄漏;
扩展知识:
说明:关于引用相关的源码 Reference、StrongReference、SoftReference、WeakReference、PhantomReference将在下一篇博文中进行分享
类型 | 回收时间 | 应用场景 |
---|---|---|
强引用 | 一直存活,除非GC Roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
软引用 | 内存不足时会被回收 | 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存 |
弱引用 | 只能存活到下一次GC前 | 生命周期很短的对象,例如ThreadLocal中的Key。 |
虚引用 | 随时会被回收, 创建了可能很快就会被回收 | 可能被JVM团队内部用来跟踪JVM的垃圾回收活动 |