1.为什么用socket而不是binder fork进程
fork不允许存在多线程。而非常巧的是Binder通讯偏偏就是多线程,所以干脆父进程(Zgote)这个时候就不使用binder线程
2. ThrealLocal内存泄漏问题;转载自:
现在针对这个图,一步一步的分析问题,中途会得出一些临时的结论,但是最终的结论才是正确的。
1为什么会出现内存泄露
现在有2点假设,本小节的分析都是基于这两个假设之上的:
1.Entry的key使用强引用,key对ThreadLocal对象使用强引用,也就是上面图中连线5是强引用(key强引用ThreadLocal对象);
2.ThreadLocalMap中不会对过期的Entry进行清理。
上面代码中,如果ThreadLocalMap的key使用强引用,那么即使栈内存的ThreadLocal引用被清除,但是堆中的ThreadLocal对象却并不会被清除,这是因为ThreadLocalMap中Entry的key对ThreadLocal对象是强引用。
如果当前线程不结束,那么堆中的ThreadLocal对象将会一直存在,对应的内存就不会被回收,与之关联的Entry也不会被回收(Entry对应的value也不会被回收),当这种情况出现数量比较多的时候,未释放的内存就会上升,就可能出现内存泄漏的问题。
上面的结论是暂时的,有前提假设!!!最终结论还需要看后面分析。
2若Entry使用弱引用
仍旧有1个假设,就是ThreadLocalMap中不会对过期的Entry进行清理,陈旧的Entry是指Entry的key为null。
按照源码,Entry继承弱引用,其Key对ThreadLocal是弱引用,也就是上图中连线5是弱引用,连线6仍为强引用。
同样以上面代码为例,step2和step3创建了ThreadLocal对象,step2和step3执行完后,栈中的ThreadLocal引用被清除了;由于堆内存中ThreadLocalMap的Entry key弱引用ThreadLocal对象,根据垃圾收集器对弱引用对象的处理:
当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
此时堆中ThreadLocal对象会被gc回收(因为之前还有栈中的ThreadLocal ref强引用ThreadLocal对象,但现在ThreadLocal ref被清除了,于是就没有对ThreadLocal的强引用,只有一个弱引用ThreadLocal对象),Entry的key为null,但是value不为null,且value也是强引用(连线6),所以Entry仍旧不能回收,只能释放ThreadLocal的内存,仍旧可能导致内存泄漏。
在没有自动清理陈旧Entry的前提下,即使Entry使用弱引用,仍可能出现内存泄漏。
3弱引用配合自动回收
通过3.2的分析,其实只要陈旧的Entry能自动被回收,就能解决内存泄漏的问题,其实JDK就是这么做的。
如果看过源码,就知道,ThreadLocalMap底层使用数组来保存元素,使用“线性探测法”来解决hash冲突,关于线性探测法的介绍可以查看:利用线性探测法解决hash冲突
在每次调用ThreadLocal类的get、set、remove这些方法的时候,内部其实都是对ThreadLocalMap进行操作,对应ThreadLocalMap的get、set、remove操作。
重点来了!重点来了!重点来了!
ThreadLocalMap的每次get、set、remove,都会清理过期的Entry,下面以get操作解释,其他操作也是一个意思,大致如下:
1.ThreadLocalMap底层用数组保存元素,当get一个Entry时,根据key的hash值(非hashCode)计算出该Entry应该出在什么位置;
2.计算出的位置可能会有冲突,比如预期位置是position=5,但是position=5的位置已经有其他Entry了;
3.出现冲突后,会使用线性探测法,找position=6位置上的Entry是否匹配(匹配是指hash相同),如果匹配,则返回position=6的Entry。
4.在这个过程中,如果position=5位置上的Entry已经是陈旧的Entry(Entry的key为null),此时position=5的key就应该被清理;
5.光清理position=5的Entry还不够,为了保证线性探测法的规则,需要判断数组中的其他元素是否需要调整位置(如果需要,则调整位置),在这个过程中,也会进行清理陈旧Entry的操作。
上面这5个步骤就保证了每次get都会清理数组中(map)的陈旧Entry,清理一个陈旧的Entry,就是下面这三行代码:
| 123 | Entry.value = ``null``; ``// 将Entry的value设为null``table[index] = ``null``;``// 将数组中该Entry的位置设置null``size--; ``// map的size减一 |
|---|
对于ThreadLocal的set、remove也类似这个原理。
有了自动回收陈旧Entry的操作,需要注意的是,在这个时候,key使用弱引用就是至关重要的一点!!!
因为key使用弱引用后,当弱引用的ThreadLocal对象被会回收后,该key的引用为null,则该Entry在下一次get、set、remove的时候就才会被清理,从未避免内存泄漏的问题。
4 addView层级
public void addView(View child, LayoutParams params) {
addView(child, -1, params);
}
默认加在最后即展示在最顶层。
所以当需要后面添加的view不覆盖之前的view可以添加view的时候使用
addView(child, 0, params);
eg:闪屏广告后面加的广告界面将之前的跳过按钮覆盖,造成了bug.
4总结
在上面的分析中,看到ThreadLocal基本不会出现内存泄漏的问题了,因为ThreadLocalMap中会在get、set、remove的时候清理陈旧的Entry,与Entry的key使用弱引用密不可分。
当然我们也可以在代码中手动调用ThreadLocal的remove方法进行清除map中key为该threadLocal对象的Entry,同时清理过期的Entry。