ThreadLocal内存泄露
内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出 out of memory :没内存可以分配给新的对象了。
我们知道,线程Thread对象中,每个线程对象内部都有一个的ThreadLocalMap对象。如果这个对象存储了多个大对象,则可能早出内存溢出OOM。为了防止这种情况发生,在ThreadLocal的源码中,有对应的策略,即调用 get()、set()、remove() 方法,均会清除 ThreadLocal内部的 内存。
ThreadLocal的内部是ThreadLocalMap。ThreadLocalMap内部是由一个Entry数组组成。Entry类的构造函数为 Entry(弱引用的ThreadLocal对象, Object value对象)。因为Entry的key是一个弱引用的ThreadLocal对象,所以在 垃圾回收 之前,将会清除此Entry对象的key。那么, ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value。这些 value 被Entry对象引用,所以value所占内存不会被释放。若在指定的线程任务里面,调用ThreadLocal对象的get()、set()、remove()方法,可以避免出现内存泄露。
下图虚线表示弱引用。ThreadLocal对象被GC回收了,那么key变成了null。Map又是通过key拿到的value的对象。所以,GC在回收了key所占内存后,没法访问到value的值,因为需要通过key才能访问到value对象。另外,如图所示的引用链:CurrentThread -- Map -- Entry -- value ,所以,在当前线程没有被回收的情况下,value所占内存也不会被回收。所以可能会造成了内存溢出。
在下面的图片中,展示了Thread、ThreadLocalMap、Entry、ThreadLocal的基本架构。
虚线表示是弱引用。弱引用只要继承WeakReference<T>类即可。所以说,当ThreadLocal对象被GC回收了以后,Entry对象的key就变成null了。这个时候没法访问到 Object Value了。并且最致命的是,Entry持有Object value。所以,value的内存将不会被释放。
因为上述的原因,在ThreadLocal这个类的get()、set()、remove()方法,均有实现回收 key 为 null 的 Entry 的 value所占的内存。所以,为了防止内存泄露(没法访问到的内存),在不会再用ThreadLocal的线程任务末尾,调用一次 上述三个方法的其中一个即可。
因此,可以理解到为什么JDK源码中要把Entry对象,用 弱引用的ThreadLocal对象,设计为key,那是因为要手动编写代码释放ThreadLocalMap中 key为null的Entry对象。
GC什么时候回收弱引用的对象?弱引用对象是存活到下一次垃圾回收发生之前对象。
综上:JVM就会自动回收某些对象将其置为null,从而避免OutOfMemory的错误。弱引用的对象可以被JVM设置为null。我们的代码通过判断key是否为null,从而 手动释放 内存泄露的内存。
为什么要将ThreadLocal设计为弱引用?
答:因为弱引用的对象的生命周期直到下一次垃圾回收之前被回收。弱引用的对象将会被置为null。我们可以通过判断弱引用对象是否已经为null,来进行相关的操作。在ThreadLocalMap中,如果键ThreadLocal已经被回收,说明ThreadLocal对象已经为null,所以其对应的值已经无法被访问到。这个时候,需要及时手动编写代码清理掉这个键值对,防止内存泄露导致的内存溢出。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
强引用: 不会被回收的内存。
软引用: 内部不足的时候回收的内存。
弱引用: 存活到垃圾回收前的内存。
你看我都这么努力的分享知识给你了,鼓励一下又何妨O(∩_∩)O
你的打赏是对我最好的支持!
.