其他
内存泄露的原因找到了,罪魁祸首居然是 Java TheadLocal
The following article is from 爱笑的架构师 Author 雷架
作者 | 雷架
来源 | 爱笑的架构师(ID:DancingOnYourCode)
ThreadLocal使用不规范,师傅两行泪
public class ThreadPoolDemo {
private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; ++i) {
poolExecutor.execute(new Runnable() {
@Override
public void run() {
ThreadLocal<BigObject> threadLocal = new ThreadLocal<>();
threadLocal.set(new BigObject());
// 其他业务代码
}
});
Thread.sleep(1000);
}
}
static class BigObject {
// 100M
private byte[] bytes = new byte[100 * 1024 * 1024];
}
}
创建一个核心线程数和最大线程数都为10的线程池,保证线程池里一直会有10个线程在运行。 使用for循环向线程池中提交了100个任务。 定义了一个 ThreadLoca 类型的变量,Value 类型是大对象。 每个任务会向 threadLocal 变量里塞一个大对象,然后执行其它业务逻辑。 由于没有调用线程池的 shutdown 方法,线程池里的线程还是会在运行。
ThreadLocal 的 value 值存在哪里?
static class ThreadLocalMap {
// 定义一个table数组,存储多个threadLocal对象及其value值
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
// 定义一个Entry类,key是一个弱引用的ThreadLocal对象
// value是任意对象
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 省略其他
}
1、ThreadLocal 类 set 方法
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 省略其他方法
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
//省略其他
}
2、ThreadLocal 类 get 方法
class ThreadLocal<T> {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
}
获取当前线程的 ThreadLocalMap 实例; 如果不为空,以当前 ThreadLocal 实例为 key 获取 value; 如果 ThreadLocalMap 为空或者根据当前 ThreadLocal 实例获取的 value 为空,则执行 setInitialValue();
ThreadLocal 相关类的关系总结
每个线程是一个 Thread 实例,其内部维护一个 threadLocals 的实例成员,其类型是 ThreadLocal.ThreadLocalMap。 通过实例化 ThreadLocal 实例,我们可以对当前运行的线程设置一些线程私有的变量,通过调用 ThreadLocal 的 set 和 get 方法存取。 ThreadLocal 本身并不是一个容器,我们存取的 value 实际上存储在ThreadLocalMap 中,ThreadLocal 只是作为 TheadLocalMap 的 key。 每个线程实例都对应一个 TheadLocalMap 实例,我们可以在同一个线程里实例化很多个 ThreadLocal 来存储很多种类型的值,这些 ThreadLocal 实例分别作为 key,对应各自的 value,最终存储在 Entry table 数组中。 当调用 ThreadLocal 的 set/get 进行赋值/取值操作时,首先获取当前线程的 ThreadLocalMap 实例,然后就像操作一个普通的 map一样,进行put 和 get。
ThreadLocal 内存模型原理
线程运行时,我们定义的 TheadLocal 对象被初始化,存储在 Heap,同时线程运行的栈区保存了指向该实例的引用,也就是图中的ThreadLocalRef。 当 ThreadLocal 的 set/get 被调用时,虚拟机会根据当前线程的引用也就是 CurrentThreadRef 找到其对应在堆区的实例,然后查看其对用的TheadLocalMap 实例是否被创建,如果没有,则创建并初始化。 Map 实例化之后,也就拿到了该 ThreadLocalMap 的句柄,那么就可以将当前 ThreadLocal 对象作为 key,进行存取操作。 图中的虚线,表示 key 对应 ThreadLocal 实例的引用是个弱引用。
强引用弱引用的概念
static class ThreadLocalMap {
// 定义一个Entry类,key是一个弱引用的ThreadLocal对象
// value是任意对象
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 省略其他
}
1、强引用
2、弱引用
3、软引用
4、虚引用
内存泄露是不是弱引用的锅?
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. 为了处理非常大和长期的用途,哈希表条目使用weakreference作为键。
ThreadLocal 最佳实践
当需要存储线程私有变量的时候。 当需要实现线程安全的变量时。 当需要减少线程资源竞争的时候。
每日打卡赢积分兑换书籍入口
👇🏻👇🏻👇🏻