ThreadLocal类第一次使用导致内存溢出

Posted daiveking

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal类第一次使用导致内存溢出相关的知识,希望对你有一定的参考价值。

ThreadLocal类的理解

问题来源

      前段时间开发一个新任务,发现一个问题,生产环境业务中读取会员信息,出现读到其他会员的现象,就是同一个会员ID能查出多个会员信息,一开始并没有打日志,我怀疑是数据问题,产品说ID是唯一的,于是我打了日志,发现会员信息有时候正常,有时候不正常,在单机测试环境下没有问题,但是我并没有打印直接返回的对象,我开始怀疑是不是并发的问题,我在想是不是ThreadLocal的问题,我问了之前开发的同事,他们说大家都这么用不可能有问题,我再次把日志打的更加详细,把缓存查询的所有对象信息全部打出来,发现问题了,跟之前现象一样,时而读到其他用户信息,时而读到该会员的信息,我确认就是ThreadLocal的问题,我网上搜了一下,果然是TreadLocal内存泄漏问题,使用完ThreadLocal,没有调用remove方法,导致新的线程拿到线程池中未销毁的线程的会员信息,还好功能影响不大,否则要背大锅了,也是第一次使用ThreadLocal,写篇文章加深记忆。

ThreadLocal类是什么

       从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

      从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:

      1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束(我们就是为了避免在方法调用中来回传递参数,把大量查询出来的对象放在ThreadLocal)。

      2、线程间数据隔离

      3、进行事务操作,用于存储线程事务信息。

      4、数据库连接,Session会话管理。

现在相信你已经对ThreadLocal有一个大致的认识了,下面我们看看如何用?

ThreadLocal类是怎么用

最简单的例子(网上很多人说get方法报空指针,我直接测试不会抛出异常):

 技术图片

打印结果:

ThreadLocal1:null

ThreadLocal2:ThreadLocal test

场景例子:

 技术图片

可以看出:使用非常简单,只要基于本线程的数据,都可以保存在ThreadLocal中

ThreadLocal源码理解

get方法:代码很简单,直接获取该线程为key的TreadLocalMap的值

 技术图片

 

Set方法:

 技术图片

 

Remove方法:

 技术图片

 

TreadLocalMap的定义:

我们可以看到ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value

 技术图片

 

其实TreadLocalMapTreadLocal 的一个内部静态类 TreadLocalMap就是用来存储数据的对象,实现跟HashMap类似。

OK,其实内部源码很简单,现在我们总结一波

(1)每个Thread维护着一个ThreadLocalMap的引用

(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

(5)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

OK,现在从源码的角度上不知道你能理解不,对于ThreadLocal来说关键就是内部的ThreadLocalMap。

ThreadLocal内存溢出分析

只要是介绍ThreadLocal的文章都会帮大家认识一个点,那就是内存泄漏问题。我们先来看下面这张图。

 技术图片

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocalnull了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMapkey没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

 

以上是关于ThreadLocal类第一次使用导致内存溢出的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal 内存泄露的根本原因

JVM内存泄漏导致内存溢出(OOM)的场景

Java前提下, MySQL数据库,一次性存储大量数据导致内存溢出

记一次内存溢出查找分析文档

ThreadLocal 源码分析

ThreadLocal的内存泄露分析以及如何避免?