ThreadLocal内存泄漏

2020-09-27  本文已影响0人  拿拿guardian

ThreadLocal是为了解决对象不能被多线程共享访问的问题,通过ThreadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这样每个线程使用自己的对象实例,彼此不会影响达到隔离的作用,从而就解决了对象在被共享访问带来线程安全问题。

内存泄漏问题

每个线程都持有一个threadLocals实例,threadLocals的生命周期同线程个生命周期一样长。只要线程还存活,threadLocals实例就会被当前线程一直以强引用的方式持有。但是threadLocals的Key是以弱引用的方式被Entry所持有的,系统GC时,会被回收。所以会存在Entry的key为空但是value不为空的情况。Entry的key为空,导致Entry的value无法被访问到,但又无法被回收,所就内存泄漏了。

优化

在ThreadLocal的set和get方法中都有相应的处理。针对key为null的entry,有一定概率会清除其对应的无用的value。

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); // 这一步会对key=null,value!=null的场景中的value值做清空

                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

为什么使用弱引用

如果使用强引用,在业务代码中执行threadLocalInstance==null操作,以清理掉threadLocal实例的目的,但是因为threadLocalMap的Entry强引用threadLocal,因此在gc的时候进行可达性分析,threadLocal依然可达,对threadLocal并不会进行垃圾回收,依然会内存泄漏。相对而言,使用弱引用虽然有存在内存泄漏的可能,但是代码上已经做了优化来保证内存泄漏的发生概率尽可能低。

上一篇下一篇

猜你喜欢

热点阅读