高级JAVA开发 Java基础部分

高级JAVA开发 Java基础部分

  • ThreadLocal 以及内存泄漏问题

ThreadLocal 以及内存泄漏问题

分析如有不正确之处,请回帖或联系我:[email protected]

随便找一个应用ThreadLocal的例子:
高级JAVA开发 Java基础部分_第1张图片
在使用时,声明一个本地ThreadLocal变量并重写initialValue()方法,就可以为每个线程提供一个本地变量存储的功能。那么,它是怎么实现的呢?
在使用时调用ThreadLocal.get()方法,看看get方法的实现:
高级JAVA开发 Java基础部分_第2张图片
get方法用当前线程作为key获得了一个map,那么看看这个map是哪儿来的,跟进getMap()方法:
在这里插入图片描述高级JAVA开发 Java基础部分_第3张图片
由图可知,map是从Thread类取得的,这个map是Thread类的一个成员变量!
那么重新整理下思路:Thread类包含了一个map类型的成员变量,默认是null,ThreadLocal相当于一个工具类,提供对这个map的访问方法,那么这个map是什么时候被初始化的呢?当我们用ThreadLocal.get()方法时,拿到的不是null,那返回get()方法查看原因:
高级JAVA开发 Java基础部分_第4张图片
高级JAVA开发 Java基础部分_第5张图片
这里看出,当map不是null时用当前ThreadLocal对象作为key去取value,map是null时,初始化map后,调用我们重写的initialValue(),把initialValue放到map,然后返回initialValue。
那createMap都干了点啥???继续跟:
高级JAVA开发 Java基础部分_第6张图片
高级JAVA开发 Java基础部分_第7张图片
高级JAVA开发 Java基础部分_第8张图片
到这里总结一下:第一次使用get时候初始化了map,也就是Thread类中的map被初始化,并且以当前ThreadLocal实例的弱引用作为key,initialValue返回值作为value放到map中。第二次get则用ThreadLocal对象作为key取得value返回。实例引用关联关系为:Thread强引用Map,map中存储弱引用ThreadLocal(key)和强引用Value
弱引用的生命周期:被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
Thread不死,Map也不死,ThreadLocal对象在声明时一直被强引用着,GC就不会回收ThreadLocal,Map中的Entry拥有ThreadLocal的弱引用和Value的强引用,如果持有ThreadLocal的人释放了强引用,它唯一的弱引用会被GC掉。
恍然大悟!只有当前Thread重新new一个ThreadLocal对象覆盖原声明,旧的ThreadLocal再也没人持有它的强引用会被GC掉,此时的Value还被Entry持有着强引用没法被GC,就发生了内存泄漏!
而在我们程序中大多将ThreadLocal声明为static final的,因为有static,它的生命周期变为和当前Class的生命一样,只有当前Class被卸载的时候,可达分析时static对象不可达会被回收,最大生命化当前ThreadLocal对象。被声明为final的变量不可更改。如此声明ThreadLocal的原因大致是不想让它轻易消亡,而又能防止内存泄漏吧~

失效的Value何时被回收?
模拟一下,ThreadLocal被替换掉后,在我们自己的程序中必然会重新重写initialValue方法,调用get()方法后是如何工作的:
高级JAVA开发 Java基础部分_第9张图片
高级JAVA开发 Java基础部分_第10张图片
高级JAVA开发 Java基础部分_第11张图片
高级JAVA开发 Java基础部分_第12张图片
cleanSomeSlots方法注释写到:
高级JAVA开发 Java基础部分_第13张图片
翻译:试探性地扫描一些cell,寻找旧的entry。在添加新元素或删除另一个旧元素时调用此函数。它执行对数次扫描,在不扫描(快速但保留垃圾)和扫描次数与元素数量成比例之间保持平衡,这将找到所有垃圾,但会导致一些插入花费O(n)时间。
高级JAVA开发 Java基础部分_第14张图片
到这里已经了解到在get和set操作的同时会删除无效的Entry。

最终总结:如果ThreadLocal被新new了一个,Thread成员变量map中的弱引用key会被回收,value所在的Entry会在下一次调用新ThreadLocal的get或者set方法时被擦除,Entry对象由于没有其他强引用等着被GC掉。如果在覆盖旧ThreadLocak对象前想加速GC,ThreadLocal提供了remove方法,remove方法会删除key的引用后再调用expungeStaleEntry(这里就不再贴图了),最终释放Entry的引用,帮助GC。

你可能感兴趣的:(技术栈)