重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章给大家介绍threadLocal出现内存泄漏的原因是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
创新互联建站成立10余年来,这条路我们正越走越好,积累了技术与客户资源,形成了良好的口碑。为客户提供做网站、成都网站制作、网站策划、网页设计、域名注册、网络营销、VI设计、网站改版、漏洞修补等服务。网站是否美观、功能强大、用户体验好、性价比高、打开快等等,这些对于网站建设都非常重要,创新互联建站通过对建站技术性的掌握、对创意设计的研究为客户提供一站式互联网解决方案,携手广大客户,共同发展进步。
内存泄漏的现象执行 cn.enjoyedu.ch2.threadlocal 下的 ThreadLocalOOM,并将堆内存大小设 置为-Xmx256m, 我们启用一个线程池,大小固定为 5 个线程
场景 1:首先任务中不执行任何有意义的代码,当所有的任务提交执行完成 后,可以看见,我们这个应用的内存占用基本上为 25M 左右
场景 2:然后我们只简单的在每个任务中 new 出一个数组,执行完成后我们 可以看见,内存占用基本和场景 1 同
场景 3:当我们启用了 ThreadLocal 以后:,执行完成后我们可以看见,内存占用变为了 100M 左右
场景 4:于是,我们加入一行代码,再执行,看看内存情况:
可以看见,内存占用基本和场景 1 同。 这就充分说明,场景 3,当我们启用了 ThreadLocal 以后确实发生了内存泄 漏。
分析 :
根据我们前面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需 要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。因此使用了 ThreadLocal 后,引用链如下图:
图中的虚线表示弱引用。
这样,当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。导致ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前 线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强 引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> 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 内存泄漏。
key 使用弱引用:对 ThreadLocal 对象实例的引用被被置为 null 了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 的 对象实例也会被回收。value 在下一次 ThreadLocalMap 调用 set,get,remove 都 有机会被回收。
比较两种情况
我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可 以多一层保障。
因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引 用。
总结:
JVM 利用设置 ThreadLocalMap 的 Key 为弱引用,来避免内存泄露。
JVM 利用调用 remove、get、set 方法的时候,回收弱引用。
当 ThreadLocal 存储很多 Key 为 null 的 Entry 的时候,而不再去调用 remove、 get、set 方法,那么将导致内存泄漏。
使用线程池+ ThreadLocal 时要小心,因为这种情况下,线程是一直在不断的 重复运行的,从而也就造成了 value 可能造成累积的情况。
关于threadLocal出现内存泄漏的原因是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。