ThreadLocal内存泄漏原因
-
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();
- 代码结构
画图结合使用逻辑,就清楚了。
假设我们自己的代码中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变量,单线程:
-
如灵魂画手,即,该线程下的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,即每次设置值都会尝试清理清理)。