赞
踩
如果我们在使用ThreadLocal的过程中发现有内存泄漏的情况,是不是这个内存泄漏跟Entry中使用弱引用的key有关系?下面我们先来复习下内存泄漏和弱引用相关的知识,在分析。
Java中的引用类型有4种类型:强、软、弱、虚。当前问题主要涉及强引用和弱引用。
我们来分析下如果ThreadLocalMap的Entry的key是强引用的情况,情况会如何呢?此时ThreadLocal的内存堆栈图如下4.1-1所示:
假设此时业务代码使用完ThreadLocal,ThreadLocal Ref被回收,如下图4.1-2所示
因为ThreadLocal Entry中的key强引用了ThreadLocal导致ThreadLocal对象无法被回收
在没有手动删除这个Entry以及当前线程运行的情况下,始终有强引用链,CurrentThread Ref->CurrentThread->ThreadLocalMap->Entry(entry中包括key引用和value),导致Entry内存泄露。
ThreadLocalMap Entry中的key如果使用强引用,无法完全避免内存泄漏。
演示下key为弱引用的情况,如下图4.2-1所示:
以上两种情况,内存泄漏的发生跟ThreadLocalMap key是否使用弱引用没有必然联系,那么真正原因是什么呢?
第一点如果在使用完ThreadLocal并调用其remove方法删除对应的Entry,可以避免内存泄漏。
第二点ThreadLocalMap 是Thread的一个属性,被当前线程引用,所以它的生命周期同Thread一样。如果在使用完ThreadLocal同时,线程结束运行,ThreadLocalMap也会被GC回收,根源上避免了内存泄露。
综上,THreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期同Thread一样长,在使用完ThreadLocal后如果没有手动删除对应的Entry就会导致内存泄漏。
根据上面的分析,Entry的key无论是使用强弱引用,都可能导致ThreadLocal的内存泄漏。避免内存泄露的方法:
显然相对于第一种方式,第二种方式更不好控制,特别是在使用线程池的情况下,线程结束执行是不会被销毁的。
而我们只要在使用完ThreadLocal后,调用remove方法,无论Entry的key是强引用还是弱引用,都不会出现ThreadLocal内存泄露的情况,为什么我们还要使用弱引用呢?
根据我们之前对ThreadLocal源码的分析,ThreadLocalMap的set、getEntry方法中,都会对key为null的entry进行标记回收(非实时),即吧key为null对应entry的vualue置为null。这意味着,在当前线程依然运行的前提下,就算没有忘记调用remove方法,弱引用比强引用可以多一层保障:在ThreadLocal引用被回收之后,对应Entry中的key的弱引用ThreadLocal被回收,对应的value在下一次ThreadLocalMap调用set、get或者remove任一方法的时候会被清除,从而避免内存泄漏。
虽然key为弱引用为避免内存泄漏提供一层保障,但不是实时的,需要在下次调用ThreadLocalMap的相应的方法的时候才会被清除。所以在ThreadLocal使用完成后,强烈建议调用remove方法。
首先我们来看下ThreadLocalMap是如果计算hash的,构造方法如下:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 计算hash
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
这里通过定义一个AtomicInteger类型,每次获取当前值加上HASH_INCREMENT,这个值跟斐波那契数列(黄金分割数)有关,主要目的使哈希码均匀的分布在 2 n 2^n 2n的数组中,尽量避免hash冲突。
计算hash的时候采用hashcode&(size-1)的算法,这相当于hashcode%size 的一个更高效的实现。因此采用该算法,我们要求size必须是2的整次幂,这也能保证在索引不越界的前提下,使得hash冲突的次数减少。
关于hash、散列以及黄金分割数更深入的知识,本人目前没学习,不做讨论啊声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/954984
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。