preface

When can a ThreadLocal leak occur? Static class Entry extends WeakReference > {}, the answer is actually to use WeakReference. >

Summary of this article

  • Object o = new Object()
  • SoftReference: new SoftReference(o);
  • WeakReference: new WeakReference(o);
  • Virtual reference: new PhantomReference(o);
  • The use of ThreadLocal and the cause of memory leaks

Jdk 1.2 added abstract class Reference, SoftReference, WeakReference, and PhantomReference, expanded the Reference type classification, and achieved more fine-grained control over memory.

For example, when we run out of memory for our cache data, I want the cache to free up memory, or put the cache out of the heap, etc.

But how do we distinguish which objects need to be collected (garbage collection algorithm, reachability analysis), when to collect, and when to collect so that we can be notified of the collection? JDK 1.2 introduced these reference types.

Reference types When to recycle
Strong reference Strongly referenced objects, as long as GC root is reachable, will not be reclaimed and run out of memory, will throw OOM
SoftReference: SoftReference Soft reference objects, in GC root, only soft references can reach object A, which will be collected by garbage collection before OOM
WeakReference: WeakReference Weak references: In GC root, only weak references can reach c and c will be reclaimed if GC occurs
Virtual Reference: PhantomReference The virtual reference must be used in conjunction with the ReferenceQueue. It is not known when the reference is recycled, but after recycling, the ReferenceQueue can be operated to obtain the reference being recycled

Strong reference

Strong references are often used: Object o = new Object(). In garbage collection, strongly referenced variables are not collected, and only when o=null is set, the JVM passes reachabability analysis, and no GC root has reached the object, will the garbage collector clean up the heap and free memory. If you continue to request memory allocation, you will receive oom.

Define a class Demo, Demo instance occupies the memory size of 10m, keep adding Demo examples to list, because cannot apply for memory allocation, the program throws OOM termination

// -Xmx600m
public class SoftReferenceDemo {
    // 1m
    private static int _1M = 1024 * 1024 * 1;
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Object> objects = Lists.newArrayListWithCapacity(50);
        int count = 1;
        while (true) {
            Thread.sleep(100);
            // Get how much free memory the JVM has
            long meme_free = Runtime.getRuntime().freeMemory() / _1M;
            if ((meme_free - 10) > =0) {
                Demo demo = new Demo(count);
                objects.add(demo);
                count++;
                demo = null;
            }
            System.out.println("JVM free memory" + meme_free + " m"); System.out.println(objects.size()); }}@Data
    static class Demo {
        private byte[] a = new byte[_1M * 10];
        private String str;
        public Demo(int i) {
            this.str = String.valueOf(i); }}}Copy the code

Above code run results, throw OOM program stop

JVM free memory41 m
54
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.fly.blog.ref.SoftReferenceDemo$Demo.<init>(SoftReferenceDemo.java:37)
	at com.fly.blog.ref.SoftReferenceDemo.main(SoftReferenceDemo.java:25)
Copy the code

However, some business scenarios require us to run out of memory, which can release some unnecessary data. Like the user information we store in the cache.

Soft references

SoftReference is one of the classes that GC root sends to object A (SoftReference) before the JVM OOM. Is released by the JVM GC.

Add data of about 10 MB to the List in an infinite loop, and no OOM is displayed.

// -Xmx600m
public class SoftReferenceDemo {
    // 1m
    private static int _1M = 1024 * 1024 * 1;
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Object> objects = Lists.newArrayListWithCapacity(50);
        int count = 1;
        while (true) {
            Thread.sleep(500);
            // Get how much free memory the JVM has
            long meme_free = Runtime.getRuntime().freeMemory() / _1M;
            if ((meme_free - 10) > =0) {
                Demo demo = new Demo(count);
                SoftReference<Demo> demoSoftReference = new SoftReference<>(demo);
                objects.add(demoSoftReference);
                count++;
                // If the Demo is null, only one reference reaches the demo instance. GC will reclaim the Demo instance before OOM
                demo = null;
            }
            System.out.println("JVM free memory" + meme_free + " m"); System.out.println(objects.size()); }}@Data
    static class Demo {
        private byte[] a = new byte[_1M * 10];
        private String str;
        public Demo(int i) {
            this.str = String.valueOf(i); }}}Copy the code

Using JVisualVM to look at the JVM heap usage, you can see that the heap is reclaimed when it is about to overflow. When you have a large amount of free memory, you actively perform garbage collection and the memory is not reclaimed.

A weak reference

When only WeakReference of object Demo is reachable, demo will be reclaimed after GC to release the memory.

The following programs also run continuously, but at different times

// -Xmx600m -XX:+PrintGCDetails
public class WeakReferenceDemo {
    // 1m
    private static int _1M = 1024 * 1024 * 1;

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Object> objects = Lists.newArrayListWithCapacity(50);
        int count = 1;
        while (true) {
            Thread.sleep(100);
            // Get how much free memory the JVM has
            long meme_free = Runtime.getRuntime().freeMemory() / _1M;
            if ((meme_free - 10) > =0) {
                Demo demo = new Demo(count);
                WeakReference<Demo> demoWeakReference = new WeakReference<>(demo);
                objects.add(demoWeakReference);
                count++;
                demo = null;
            }
            System.out.println("JVM free memory" + meme_free + " m"); System.out.println(objects.size()); }}@Data
    static class Demo {
        private byte[] a = new byte[_1M * 10];
        private String str;
        public Demo(int i) {
            this.str = String.valueOf(i); }}}Copy the code

As a result, when the available memory of SoftReference is about to be exhausted, the memory will be released, and when the available memory of WeakReference reaches about 360M, the memory will be garbage released

[PSYoungGen: 129159K->1088K] 129175K->1104K(502784K), 0.0007990 secs] [Times: User =0.00 sys=0.00, real=0.00 SECs] JVM free memory 364 M 36 JVM free memory 477 MCopy the code

Phantom reference

There are also phantomreferences, because you don’t know when they’re being recycled, and you have to coordinate with ReferenceQueue, which is the queue from which you get an instance of the PhantomReference when the object is recycled.

// -Xmx600m -XX:+PrintGCDetails
public class PhantomReferenceDemo {
    // 1m
    private static int _1M = 1024 * 1024 * 1;

    private static ReferenceQueue referenceQueue = new ReferenceQueue();

    public static void main(String[] args) throws InterruptedException {
        ArrayList<Object> objects = Lists.newArrayListWithCapacity(50);
        int count = 1;
        new Thread(() -> {
            while (true) {
                try {
                    Reference remove = referenceQueue.remove();
                    Memory is not free in time, we need to get that Demo in the team to be recycled, and then
                    // Remove this object from objects
                    if (objects.remove(remove)) {
                        System.out.println("Remove elements"); }}catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        while (true) {
            Thread.sleep(500);
            // Get how much free memory the JVM has
            long meme_free = Runtime.getRuntime().freeMemory() / _1M;
            if ((meme_free - 10) > 40) {
                Demo demo = new Demo(count);
                PhantomReference<Demo> demoWeakReference = new PhantomReference<>(demo, referenceQueue);
                objects.add(demoWeakReference);
                count++;
                demo = null;
            }
            System.out.println("JVM free memory" + meme_free + " m"); System.out.println(objects.size()); }}@Data
    static class Demo {
        private byte[] a = new byte[_1M * 10];
        private String str;

        public Demo(int i) {
            this.str = String.valueOf(i); }}}Copy the code

ThreadLocal

We use ThreadLocal a lot in our actual development. So what exactly is it (thread-local variables), we know about local variables (variables defined in methods) and member variables (class attributes).

Sometimes we want the lifetime of a variable to last through the lifetime of a thread (threads in the thread pool can be assigned to perform different tasks), and we can pick up the preset variable on each method call. This is what ThreadLocal does.

For example, if we want to get the HttpServletRequest of the current request, and then each method can get it, SpringBoot has wrapped it for us, RequestContextFilter after each request comes in, RequestContextHolder is used to set thread-local variables by manipulating ThreadLocal.

ThreadLocal is only for calls in the current thread. Cross-thread calls don’t work, so the Jdk uses InheritableThreadLocal to implement this.

ThreadLocal retrieves information about the currently requested user

How does TheadLocal work

/ * * *@authorZhang qin * climbing@date2018/12/21 - * / space
@RestController
public class UserInfoController {
    @RequestMapping("/user/info")
    public UserInfoDTO getUserInfoDTO(a) {
        returnUserInfoInterceptor.getCurrentRequestUserInfoDTO(); }}@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
    private static final ThreadLocal<UserInfoDTO> THREAD_LOCAL = new ThreadLocal();
    // Request header user name
    private static final String USER_NAME = "userName";
    // Note that only beans injected into ioc can be injected
    @Autowired
    private IUserInfoService userInfoService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Check whether it is an interface request
        if (handler instanceof HandlerMethod) {
            String userName = request.getHeader(USER_NAME);
            UserInfoDTO userInfoByUserName = userInfoService.getUserInfoByUserName(userName);
            THREAD_LOCAL.set(userInfoByUserName);
            return true;
        }
        return false;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // Remember to free up memory when you're done with it
        THREAD_LOCAL.remove();
    }
    // Get the user information set by the current thread
    public static UserInfoDTO getCurrentRequestUserInfoDTO(a) {
        returnTHREAD_LOCAL.get(); }}@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /** * Inject the UserInfoInterceptor into the IOC container */
    @Bean
    public UserInfoInterceptor getUserInfoInterceptor(a) {
        return new UserInfoInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        Calling this method returns the ioc bean
        registry.addInterceptor(getUserInfoInterceptor()).addPathPatterns("/ * *"); }}Copy the code

InheritableThreadLocal

Sometimes we want the lifetime of a local variable in the current thread to extend to the child thread. The parent thread sets the variable and the child thread picks it up. InheritableThreadLocal provides this capability.

/ * * *@authorZhang qin * climbing@dateThe 2020-06-27 - * / lift
public class InheritableThreadLocalDemo {
    static InheritableThreadLocal<String> INHERITABLE_THREAD_LOCAL = new InheritableThreadLocal();
    static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        INHERITABLE_THREAD_LOCAL.set("Set variables using InheritableThreadLocal in parent thread");
        THREAD_LOCAL.set("Set variables in parent thread using ThreadLocal");
        Thread thread = new Thread(
                () -> {
                    // Get the set variable
                    System.out.println([InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] + INHERITABLE_THREAD_LOCAL.get());
                    // Print null
                    System.out.println(Get parent thread set variables from ThreadLocal:+ THREAD_LOCAL.get()); }); thread.start(); thread.join(); }}Copy the code

ThreadLocal get method source code analysis

You can understand that Thead object has a property Map whose key is an instance of ThreadLoal, which gets the source code for thread-local variables

public class ThreadLocal<T> {
    public T get(a) {
        // Fetch runs in that thread
        Thread t = Thread.currentThread();
        // Get the Map from Thread
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            // Use the ThreadLocal instance to get values from the Map
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}// Initialize the Map and return the initialization value. The default value is null. You can define a method from which to load the initialization value
        returnsetInitialValue(); }}Copy the code

InheritableThreadLocal gets data analysis of the parent thread Settings

Each Thread also has a Map property called inheritableThreadLocals, which holds values copied from the parent Thread.

When initializing the child thread, it copies the inheritableThreadLocals Map of the parent thread to the inheritableThreadLocals Map of the parent thread. Each thread maintains its own inheritableThreadLocals, so the child thread can’t change the data maintained by the parent thread, only the child thread can get the data set by the parent thread.

public class Thread{
    
	// Maintain thread-local variables
    ThreadLocal.ThreadLocalMap threadLocals = null;

    // Maintain data from parent threads that can be inherited by child threads
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
   // Thread initialization
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if(inheritThreadLocals && parent.inheritableThreadLocals ! =null) {// Copy the inheritableThreadLocals data from the parent thread to the child thread
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }}}public class TheadLocal{
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        // create a Map of your own thread and copy the values of the parent thread into it
        return new ThreadLocalMap(parentMap);
    }

    static class ThreadLocalMap {
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            // Iterate over the parent thread to copy the data
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if(e ! =null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if(key ! =null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while(table[h] ! =null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
    }
} 
Copy the code

Demo verification, above analysis

Cause of memory leak

We define a 20-size thread pool that executes 50 tasks and sets threadLocal to null to simulate a memory leak. To eliminate distractions, I set the JVM parameter to -xMS8g -XMx8g -xx :+PrintGCDetails

public class ThreadLocalDemo {
    private static ExecutorService executorService = Executors.newFixedThreadPool(20);
    private static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 50; i++) {
            executorService.submit(() -> {
                try {
                    threadLocal.set(new Demo());
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (Objects.nonNull(threadLocal)) {
                        // To prevent memory leaks, the current thread runs out
// threadLocal.remove();}}}); } Thread.sleep(5000);
        threadLocal = null;
        while (true) {
            Thread.sleep(2000); }}@Data
    static class Demo {
        //
        private Demo[] demos = new Demo[1024 * 1024 * 5]; }}Copy the code

Run the program and no GC log is printed, indicating that no garbage collection has taken place

In Java VisualVM, we perform garbage collection. The 20 ThreadLocalDemo$Demo[] can not be collected. This is a memory leak.

The program loops 50 times to create 50 demos, and garbage collection is not triggered while the program is running (guaranteed by JVM parameters), so the number of surviving instances of ThreadLocalDemo$Demo[] is 50.

When I manually trigger GC and the number of instances drops to 20 instead of the expected zero, the program has a memory leak problem

Why is there a memory leak?

Because there is one Thread per Thread, the Thread pool size is 20. The Thread of ThreadLocal. ThreadLocalMap threadLocals = null;

ThreadLocalMap has Entry[] tables and k is a weak reference. When we set threadLocal to null, the GC ROOT to ThreadLocalDemo$Demo[] reference chain will still exist, but the k will be reclaimed, the value will still exist, and the tables length will not be reclaimed.

ThreadLocal optimizes tables[I] to null when k is null. In this way, a single Entry can be reclaimed. But when we set ThreadLocal to null, we can no longer operate on method calls. When a Thread calls another ThreadLocal again, it will rehash the Map based on the condition and delete the Entry whose K is null.

It is also convenient to solve the above problems. After using thread-local variables, a thread can call remove to actively remove Entry.


This article was created by Zhang Panqin on his blog www.mflyyou.cn/. It can be reproduced and quoted freely, but the author must be signed and indicate the source of the article.

If reprinted to wechat official account, please add the author’s official qr code at the end of the article. Wechat official account name: Mflyyou