ThreadLocal深入理解

一、实现原理

内存结构
通过每个线程维护一张ThreadLocalMap哈希映射表,key为ThreadLocal弱引用,value是Object本身。也就是说,ThreadLocal本身不存任何实际值,而是通过本身作为key,从ThreadLocalMap中获取具体的值。
实际上,ThreadLocalMap保存的是Entry(这个是底层代码实现,每个线程持有)的一个数组,通过ThreadLocalhashCode&(len-1)获取数组的游标i,具体如下

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();
}

二、内存溢出分析

由于Entry使用弱引用关联ThreadLocal对象,而线程堆栈内的是使用强引用关联ThreadLocal对象,当线程堆栈中的引用释放,遇到下次gc,ThreadLocal将被释放,也就是说,Entry的key将变成null,但此时实际的Object还是没有被释放,一直在堆内存里面,如果该线程一直没结束,那这块内存就将一直不能被回收。

解决

在ThreadLocal设计中,方法get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是还是无法完全保证内存泄漏问题,例如ThreadLocal声明称类成员静态变量,也就是说ThreadLocal的引用分配在了方法区,这种导致强引用一直会存放在类方法区,无法回收ThreadLocal实例,这种情况可能会导致内存泄漏,具体可以参见ThreadLocal内存泄漏实例

ThreadMap为什么要用弱引用

为什么使用弱引用而不是强引用?
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.(官网)

  • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
    比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

三、实践

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

参考:http://www.importnew.com/22039.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值