工作细节知识

65 阅读5分钟

1.为什么用socket而不是binder fork进程

fork不允许存在多线程。而非常巧的是Binder通讯偏偏就是多线程,所以干脆父进程(Zgote)这个时候就不使用binder线程

2. ThrealLocal内存泄漏问题;转载自:

www.cnblogs.com/-beyond/p/1…

  现在针对这个图,一步一步的分析问题,中途会得出一些临时的结论,但是最终的结论才是正确的。

 

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,就是下面这三行代码:

123Entry.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。

java Queue数据结构

juejin.cn/post/688662…