背景
-
推荐阅读ThreadLocal的基础知识及其基本使用。ThreadLocal 理解一:基础知识及其应用
-
内存泄露 (MemoryLeak)
程序在申请内存后,系统无法释放已申请的内存空间,少量的内存泄露危害可以忽略,但内存泄露慢慢堆积,后果很严重,无论多少内存,迟早会被占光。
-
内存溢出 (OutOfMemory)
程序在申请内存时,系统没有足够的空间分配。也就是,分配的内存超出了系统能给的,因此产生溢出。
-
内存泄漏最终会导致内存溢出。
引起内存泄漏例子
- 代码
- 使用
解决方案例子
- 代码
- 使用
过程分析
-
num属性变量是ThreadLocal< Integer >,是static修饰的。这个num静态变量在JVM中类加载过程是(加载->连接->初始化->使用->卸载)。使用和卸载可以不分析。
-
连接阶段是由如下三部分组成的,验证->准备->解析。
-
其中,在准备阶段,给num赋值为null, 即是 num = null。
-
初始化。num是具体的ThreadLocal< Integer >实例。静态变量实例的地址(address)放在元数据空间中,是 jdk1.8及其以后的叫法。它是直接内存。而对象实例num的实际内容还是存放在堆上的。
-
分析类之间的关系
Thread有一个属性是ThreadLocalMap。当ThreadLocal实例执行set()方法后,当前线程的ThreadLocalMap属性(threadLocals)就会被设置一个具体的实例,这个ThreadLocalMap实例是线程私有的,因此线程安全的。这个ThreadLocalMap有一个弱引用的静态内部类Entry,这个Entry存key,value。Entry< ThreadLocal t, T v >。T是泛型。并且这个弱引用只针对key(ThreadLocal), T v是强引用类型。
-
关于 num 和 v 分析
每个线程的ThreadLocalMap的Entry的key,其实都是同一个实例,由上面的代码说明就是num。对于num是否置null,还是理解它会不会被GC掉,其实是无关紧要的,因为无论启动了多少线程,num永远只有一个。但是,如果程序主动置null会加快GC。
要思考的是T,这里的T是Java中的泛型知识,本质上是我们的值v。这里的引用关系是,每个线程都有自己的Java虚拟机栈 这里对ThreadLocalMap引用,ThreadLocalMap中持有实例弱引用的Entry,而这个Entry中的key,我们可以不思考,不用考虑它。关键是泛型T的值v。我们需要在线程不用ThreadLocalMap中的值的时候,我们将v置null,目的加快GC。v是存堆内存上的,一旦,被置null后,那么下次GC的时候,一定是可以清除的。
-
分析了上面的整个过程,那么内存泄漏是怎么产生的呢?
如果我们工作线程,完成有关ThreadLocalMap中的值的计算处理过程,但是此工作线程,还有其他很多计算过程。这样的情况是存在的,那么线程将耗费一些时间执行其他的业务逻辑。在不使用ThreadLocalMap到线程结束的过程中,那么这个v将会持续被当前工作线程强引用,JVM中告诉我们如果一个对象是强引用,那么下次GC到来的时候,是不会被GC掉的,如果这个时候,eden(伊甸园)被Java进程中其他业务线程,不停地创建对象,申请堆内存。这个小小的时间段内,可能这个v被多次GC,然而都没有被GC掉。它反复地在from,to两个新生代区域移动,最终可能会被上升到老年代。假如,这个时候,系统有很多用户使用了,那么每个线程几乎都是这样,就会造成内存泄漏,因为v存在堆上,但是业务逻辑又对v不再使用。
-
后果
gc的频率多了起来,然后老年代占满了很多v的对象,假如有大量的线程,这个时候,就有重GC,我们称为Full GC,其实也是STW(stop the world)。这个时候,系统将不再接收新的请求,并且系统内的其他业务线程,全部停止执行。
由于不处理内存泄漏,最终导致JVM的重GC,其实这个过程就是内存泄漏,因为没有及时GC,慢慢就会导致内存溢出。所以,理解为内存泄漏和内存溢出都是可以的。只是不那么精确。
-
解决
调用ThreadLocal的remove()方法即可。需要注意调用的时机。 -
后续计划
会记录ThreadLocal工作过程和实现细节。
小结
- 理解什么是内存泄漏和内存溢出。
- 什么样的使用方式可能会导致内存泄漏,怎样解决。
- 内存泄漏是怎么一步一步到内存溢出的。
- 学会分析问题,探索其背后的原因。
- 一图胜千言。后面通过画图辅助自己理解ThreadLocal相关类关系,以及工作过程和实现细节。
- 解决方案:线程中对ThreadLocalMap中的T v使用完成后,立即调用remove()方法即可。