threadLocal出现内存泄漏的原因是什么

介绍

这篇文章给大家介绍threadLocal出现内存泄漏的原因是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

<强>内存泄漏的现象强执行cn.enjoyedu.ch2。threadlocal 下的 ThreadLocalOOM,并将堆内存大小设 置为-Xmx256m, 我们启用一个线程池,大小固定为 5 个线程

threadLocal出现内存泄漏的原因是什么

场景 1:首先任务中不执行任何有意义的代码,当所有的任务提交执行完成 后,可以看见,我们这个应用的内存占用基本上为 25M 左右

threadLocal出现内存泄漏的原因是什么

场景 2:然后我们只简单的在每个任务中 new 出一个数组,执行完成后我们 可以看见,内存占用基本和场景 1 同

threadLocal出现内存泄漏的原因是什么

场景 3:当我们启用了 ThreadLocal 以后:,执行完成后我们可以看见,内存占用变为了 100M 左右

threadLocal出现内存泄漏的原因是什么

场景 4:于是,我们加入一行代码,再执行,看看内存情况:

threadLocal出现内存泄漏的原因是什么

可以看见,内存占用基本和场景 1 同。 这就充分说明,场景 3,当我们启用了 ThreadLocal 以后确实发生了内存泄 漏。

分析 :

        根据我们前面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需 要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。因此使用了 ThreadLocal 后,引用链如下图:

threadLocal出现内存泄漏的原因是什么

图中的虚线表示弱引用

        这样,当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。导致ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前 线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强 引用链:Thread Ref -> 线程→ThreaLocalMap→输入→value,而这块 value 永 远不会被访问到了,所以存在着内存泄露。

        只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开, Current Thread、Map value 将全部被 GC 回收。最好的做法是不在需要使用 ThreadLocal 变量后,都调用它的 remove()方法,清除数据。

        所以回到我们前面的实验场景,场景 3 中,虽然线程池里面的任务执行完毕 了,但是线程池里面的 5 个线程会一直存在直到 JVM 退出,我们 set 了线程的 localVariable 变量后没有调用 localVariable.remove()方法,导致线程池里面的 5 个 线程的 threadLocals 变量里面的 new LocalVariable()实例没有被释放。

        其实考察 ThreadLocal 的实现,我们可以看见,无论是 get()、set()在某些时 候,调用了 expungeStaleEntry 方法用来清除 Entry 中 Key 为 null 的 Value,但是 这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。 只有 remove()方法中显式调用了 expungeStaleEntry 方法。

        从表面上看内存泄漏的根源在于使用了弱引用,但是另一个问题也同样值得 思考:为什么使用弱引用而不是强引用?

      下面我们分两种情况讨论:

            key 使用强引用:对 ThreadLocal 对象实例的引用被置为 null 了,但是 ThreadLocalMap 还持有这个 ThreadLocal 对象实例的强引用,如果没有手动删除, ThreadLocal 的对象实例不会被回收,导致 Entry 内存泄漏。

threadLocal出现内存泄漏的原因是什么