欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal内存泄漏原因

程序员文章站 2022-05-05 21:45:17
...
  • weakReference和threadLocal的基本用法

    必须放在这里,因为后面好用于解析源码的时候回忆。

//weak ref
WeakReference<Activity> ref = new WeakReference<>(activity);

//thread local
ThreadLocal<String> local = new ThreadLocal<>();
local.set("test");
String s = local.get();
local.remove();
  • 代码结构

ThreadLocal内存泄漏原因

画图结合使用逻辑,就清楚了。

假设我们自己的代码中local已经没有引用了,比如Test已经置为null,或者local置为null,可达性已经失去;

那么,作为Entry的key就被回收;而value是强引用设置的值,如果是复杂类型数据的话,就会长时间留存在ThreadLocalMap(table)数组中。在某些get(), remove() , resize() 扩容,set()动作的时候,会尝试clear部分key为null的entry,移除数组并标记value=null,去除引用。

但是这些动作并不一定能跑进去。而且假设你申请的local并不是特别频繁使用的话(即很少调用set,remove, get),这个value的引用将会存在无法回收。

解决方案:用完记得remove掉。比如Thread结束的时候,或者数据使用完后,记得remove()。有的帖子说搞成static,下面我将Handler会提到。

  • 扩展点1:多线程下有几个local?几个map?多个local变量申明呢?

    • 假设1个local变量,多线程:

      这个很容易理解了吧,每个Thread下面都挂着一个map,只是我们这一个local被多次拿去做Entry key了,保存着不同的value。

      Android Handler就是这个模式。

    • 假设多个local变量,单线程:

ThreadLocal内存泄漏原因

 如灵魂画手,即,该线程下的map的数组不再是单个Entry元素了。而是多个k v组成。
  • 假设多个local变量,多线程:

    这下好理解了,不用多说。每一个线程都有map,map下的数组就对应有多个Entry(local组成弱引用的key,和value)。

  • 扩展点2 Android Handler

    ​ 就是把Looper对象,申明了一个static的LocalThread在我们prepare的时候,进行初始化。这样的情况,就保证了每一个线程,在new出来的时候,都会创建一个独有的Looper。

    ​ 所以说,ThreadLocal和Thread有什么关系呢?Thread只和ThreadLocalMap有关,而我们申明的变量,ThreadLocal是作为ThreadLocalMap的数组中一个元素的key(weakRef)。

​ 那为什么在Handler和Looper源码中看不到remove来解决内存泄漏呢?

​ 这里他使用static来标记threadLocal变量。即保证了所有线程只有一个key。这个key永远不会被回收了。则不会出现key=null,value存在的现象。但是假设HandlerThread被回收,Looper作为value会被先回收。理论上讲,他并没有考虑内存问题。如果线程和Looper搞的多,在前面提到的逻辑中,靠的ThreadLocal内部代码的比如set,或者resize的时候,进行清理(详细可以阅读ThreadLocal.java:set()->cleanSomeSlots,即每次设置值都会尝试清理清理)。

相关标签: Android