The principle of ThreadLocal, what is the memory leak, full of dry goods

ThreadLocal principle (basic model)

ThreadLocal It provides the function of thread local variables. Its principle can be briefly summarized as the following points:

  1. Each Thread object maintains a Map with ThreadLocal as the key and any type of object as the value. This Map stores the local variable values ​​corresponding to each thread, and this Map is called ThreadLocalMap.
  2. When calling a method to set a variable value through a ThreadLocal object set(), the ThreadLocalMap of the current thread is actually obtained, and the current ThreadLocal object is used as a key, and the variable value is used as a value, and stored in the Map.
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

3. When the variable value is obtained by calling get()the method through the ThreadLocal object, it actually obtains the ThreadLocalMap of the current thread, and then uses the current ThreadLocal object as the key to obtain the corresponding value.

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

In this way, ThreadLocal achieves data isolation between threads, and each thread can independently access and modify its own local variable values ​​without affecting the variables of other threads. This is very useful in multi-threaded programming, which can avoid data competition and conflicts between threads, and improve the concurrent performance and reliability of the program.

TheadLocal transfer across threads

The value of ThreadLocal is stored independently in each thread, and other threads cannot directly obtain the value. However, you can pass the value of ThreadLocal from one thread to another by some means, for example:

  1. Manual transfer: You can get the value of ThreadLocal in one thread, and then pass the value as a parameter to another thread. This requires you to manage the delivery process and ensure thread safety yourself.
  2. When you need to set a ThreadLocal value in the parent thread and make it available in the child thread, you can use the ThreadLocal object of InheritableThreadLocaltype . InheritableThreadLocalProvides a convenient way to pass the ThreadLocal value between the parent thread and the child thread. The principle is that when the parent thread creates a child thread, the thread initialization method will pass the value of the parent thread to the child InheritableThreadLocalthread

image.png 需要注意的是,使用 ThreadLocal 进行跨线程传递值时,要确保线程安全性和正确性。在传递值的过程中,需要注意值的复制或引用传递、值的可变性、值的生命周期等问题,以避免意外的副作用和错误。

总之,虽然 ThreadLocal 的值不会自动在线程间传递,但你可以通过手动传递或使用特定的 ThreadLocal 子类来实现跨线程传递值的需求。在实际应用中,请注意线程安全性和正确性的考虑。

内存泄露原因

先从WeakReference说起

众所周知,java四种引用类型,强,软,弱,虚,先搞明白WeakReference什么时候会被gc清理,网上大多数的说法都是当GC时弱引用对象会被清理,但是需要前提条件,这点很重要!!

image.png jdk文档上写的大意就是噢,如果一个对象没有强、软引用(也要根可达),只通过弱引用(根)可达,那就可以被回收。
先搞几个测试类

image.png

image.png

weakAndSick为类变量,强引用在所以没被回收掉 image.png

强引用不在所以被回收掉 image.png

局部变量表中weakAndSickInner仍存在,存在根可达的强引用,所以回收不掉

image.png

method方法结束,局部变量引用断开,可以被回收

image.png

看看ThreadLocalMap代码结构

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

就是说entry继承WeakReference,也就是key是弱引用类型的,key对应的是ThreadLocal对象。 所以作者在这里使用弱引用类型,就是希望在ThreadLocal对象不再被使用时,ThreadLocalMap中的key中引用的ThreadLocal也就没有意义了,不要影响对ThreadLocal对象的垃圾回收!

内存泄露是针对ThreadLocalMap的,当然如果thread是临时线程用完就丢弃了,那自然可以回收ThreadLocalMap,但是如果是线程池中的常驻内存的线程呢?作为WeakReference的ThreadLocal对象可以被回收么? 实际上我们一般都将ThreadLocal对象作为一个类变量长期使用,所以依然存在强引用,在gc时仍不能回收。如果将这个强引用断开那么ThreadLocalMap的Key就可以被回收了,但是Value依然被强引用着,不能被回收。
所以在一些特殊场景下,也会有不一样的泄露的问题。 比如一个war包被部署到Tomcat中,Tomcat会加载包中的类,然后创建Servlet\Filrer这些类实例. Tomcat维护了一个线程池,每当有请求到来,这些线程对象就会去执行用于处理请求的task,也就是Filter\Servlt这些东西. 当一个war包被卸载时,Tomcat也应该释放所加载的Class和创建的Servlet/Filter实例,使之能被GC回收。
但是

一个类被GC回收需要保证以下条件都不成立:

  1. objects of that class are still reachable.
  2. the Class object representing the class is still reachable
  3. the ClassLoader that loaded the class is still reachable
  4. other classes loaded by the ClassLoader are still reachable

而ThreadLocal还保留着对象实例,导致类对象不能卸载,也会引起内存泄露。

避免 ThreadLocal 内存泄漏

  1. 了解使用场景:使用 ThreadLocal 时要了解其适用范围和生命周期,并确保在合适的时机清理和释放相关的值,及时调用 remove() 方法。
  2. 避免过度使用 ThreadLocal:仔细评估是否真正需要使用 ThreadLocal,尽量避免过度依赖它,以减少潜在的内存泄漏风险。

总之,虽然 ThreadLocal 在多线程编程中非常有用,但需要谨慎使用和管理。正确地使用和清理 ThreadLocal 的值可以避免内存泄漏问题,确保程序的性能和稳定性。

Guess you like

Origin juejin.im/post/7235117005737427001