ThreadLocal memory leak analysis and how to avoid it

Foreword

Before analyzing the memory leak caused by ThreadLocal, you need to popularize and understand the memory leak, strong reference and weak reference, and GC recycling mechanism, so as to better analyze why ThreadLocal causes memory leak? It is more important to know how to avoid this situation and enhance the robustness of the system.

Memory leak

The memory leak is that the program cannot release the applied memory space after applying for memory. The hazard of a memory leak can be ignored, but the accumulation of memory leaks is very serious.

Broadly and colloquially, it means that the memory occupied by objects or variables that will no longer be used cannot be recovered, that is, a memory leak.

Strong and weak references

Strong references , the most commonly used references, an object with a strong reference, will not be collected by the garbage collector. When the memory space is insufficient, the Java virtual machine would rather throw an OutOfMemoryError, cause the program to terminate abnormally, and not recycle such objects.

If you want to cancel the association between a strong reference and an object, you can explicitly assign the reference to null, so that the JVM will recycle the object at the appropriate time.

Weak references . When the JVM performs garbage collection, regardless of whether the memory is sufficient, the objects associated with the weak references will be collected. In java, it is represented by the java.lang.ref.WeakReference class. Weak references can be used in the cache.

GC recycling mechanism-how to find the objects that need to be recycled

There are two ways for the JVM to find the objects that need to be recycled:

  • Reference counting method: Each object has a reference counting attribute, the count is increased by 1 when a new reference is added, the count is decreased by 1 when the reference is released, and can be recycled when the count is 0

  • Reachability analysis method: search downwards from GC Roots, and the path that the search takes is called the reference chain. When an object is connected to GC Roots without any reference chain, it proves that the object is unavailable, then the virtual machine determines that it is a recyclable object.

In reference counting method, A may refer to B, and B may refer to A again, even if they are not used anymore, but because of mutual reference counter = 1, it can never be recycled.

ThreadLocal memory leak analysis

First understand some concepts from the preface (have been ignored), then we begin to formally understand the analysis of the memory leak caused by ThreadLocal.

Implementation principle

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {        /** The value associated with this ThreadLocal. */
        Object value;

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

The implementation principle of ThreadLocal, each Thread maintains a ThreadLocalMap, the key is a weakly referenced ThreadLocal instance, and the value is a copy of the thread variable. The reference relationship between these objects is as follows,

Solid arrows indicate strong references, hollow arrows indicate weak references

Cause of ThreadLocal memory leak

As can be seen from the above figure, hreadLocalMap uses the weak reference of ThreadLocal as the key. If a ThreadLocal does not have an external strong reference , Key (ThreadLocal) is bound to be recycled by GC, which will cause the key in ThreadLocalMap to be null, while the value is still There is a strong reference, only after the thead thread exits, the strong reference chain of value will be broken.

However, if the current thread does not end again, there will always be a strong reference chain for the value of these Entry keys with null:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。郑州不孕不育医院哪家好:http://www.zzfkyy120.com/

那为什么使用弱引用而不是强引用??

我们看看Key使用的

key 使用强引用

当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。郑州看不孕不育哪家好:http://jbk.39.net/yiyuanfengcai/tsyl_zztjyy/10502/

ThreadLocalMap的remove()分析

在这里只分析remove()方式,其他的方法可以查看源码进行分析:

private void remove(ThreadLocal<?> key) {    //使用hash方式,计算当前ThreadLocal变量所在table数组位置
    Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    //再次循环判断是否在为ThreadLocal变量所在table数组位置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {        if (e.get() == key) {            //调用WeakReference的clear方法清除对ThreadLocal的弱引用
            e.clear();            //清理key为null的元素
            expungeStaleEntry(i);            return;
        }
    }
}

再看看清理key为null的元素expungeStaleEntry(i):

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;    int len = tab.length;    // 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;    // 重新hash,并对table中key为null进行处理
    Entry e;    int i;    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();        //对table中key为null进行处理,将value设置为null,清除value的引用
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {            int h = k.threadLocalHashCode & (len - 1);            if (h != i) {
                tab[i] = null;                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }    return i;
}

总结

由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据

  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。郑州不孕不育医院哪家好:http://www.xbzztj.com/


Guess you like

Origin blog.51cto.com/14510351/2488802