[java] ThreadLocal memory leak code demonstration example

Posted by Myss on Tue, 15 Feb 2022 12:42:57 +0100

1. General

Reprint: ThreadLocal memory leak code demonstration example demonstration

First look at the article: Cause analysis of ThreadLocal memory leak

Related articles:

[high concurrency] ThreadLocal, InheritableThreadLocal

[Java] introduction and source code of ThreadLocal in Java

2. Cases

2.1 do not use ThreadLocal

The following program creates a thread pool with five threads.
Each thread requests a heap space of 5M.

public class MyThreadLocalOOM1 {
    public static final Integer SIZE = 500;
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, 5, 1,
            TimeUnit.MINUTES, new LinkedBlockingDeque<>());

    static class LocalVariable {//Total 5M
        private byte[] locla = new byte[1024 * 1024 * 5];
    }
    public static void main(String[] args) {
        try {
            for (int i = 0; i < SIZE; i++) {
                executor.execute(() -> {
                    new LocalVariable();
                    System.out.println("Start execution");
                });
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

After each thread has used the memory of JD ng0-5mg in the heap, it will be observed that each thread has used the memory of JD ng0-5mg. After a period of time, it will use the memory of JD ng0-5mg in the heap, which will be triggered.

At 19:30:36, I manually triggered a GC, and it can be seen that the heap space is basically released.

Description: all localvariables are released without memory leakage.

2.2 use ThreadLocal without remove

public class MyThreadLocalOOM2 {
    public static final Integer SIZE = 500;
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, 5, 1,
            TimeUnit.MINUTES, new LinkedBlockingDeque<>());

    static class LocalVariable {//Total 5M
        private byte[] locla = new byte[1024 * 1024 * 5];
    }

    static ThreadLocal<LocalVariable> local = new ThreadLocal<>();
    public static void main(String[] args) {
        try {
            for (int i = 0; i < SIZE; i++) {
                executor.execute(() -> {
                    local.set(new LocalVariable());
                    System.out.println("Start execution");
                });
                Thread.sleep(100);
            }            
            local = null;//If it is set to null here, it will still cause memory leakage
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

In the above code, the ThreadLocal variable local of static is defined, but when the for loop consistency is completed, local is set to null. Ordinary objects have no strong references at this time, and will be recycled when GC.

However, as can be seen from the figure below, even if GC is triggered manually after the for loop ends, the heap memory space still occupies about 25MB, which is exactly the sum of the LocalVariable objects of five threads in the thread pool.

So a memory leak occurred.

See ThreadLocal memory leak cause analysis for the causes of memory leak

2.3 use Thread Local and remove

public class MyThreadLocalOOM3 {
    public static final Integer SIZE = 500;
    static ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, 5, 1,
            TimeUnit.MINUTES, new LinkedBlockingDeque<>());
 
    static class LocalVariable {//Total 5M
        private byte[] locla = new byte[1024 * 1024 * 5];
    }
 
    final static ThreadLocal<LocalVariable> local = new ThreadLocal<>();
    public static void main(String[] args) {
        try {
            for (int i = 0; i < SIZE; i++) {
                executor.execute(() -> {
                    local.set(new LocalVariable());
                    System.out.println("Start execution");
                    local.remove();
                });
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

In the above code, local is called after the thread consistency is completed Remove() to delete the object in threadLocal. As can be seen in the figure below, after manually triggering GC, all memory is released without memory leakage.

2.4 single thread demo memory leak

public class MyThreadLocalOOM4 {
    public static final Integer SIZE = 500;

    static class LocalVariable {//There are 50M in total
        private byte[] locla = new byte[1024 * 1024 * 50];
    }

    static ThreadLocal<LocalVariable> local = new ThreadLocal<>();
    static LocalVariable localVariable;

    public static void main(String[] args) throws InterruptedException {
        try {
            TimeUnit.SECONDS.sleep(2);
            
            localVariable = new LocalVariable();
            local.set(new LocalVariable());
            System.out.println("Start execution");
            Thread.sleep(100);

            local = null;
            localVariable = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        while (true) {
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

From the results, we can see that the 50MB space of localVariable is released, but the 50MB space stored in ThreadLocal is not released.

3. Reference

Like the following code, Stu is defined outside for. If this stu object is used every time in a thread, there will still be a thread safety problem, because the stu object is still shared among multiple threads.

Therefore, we must add new objects in each thread to avoid multi-threaded sharing.
You can also implement MyThreadLocal to copy objects manually to avoid reusing the same object

p

ublic class MyThreadLocal<T> extends ThreadLocal<T> {
    public void set(T value) {
        String s = JSONObject.toJSONString(value);
        super.set((T) JSONObject.parseObject(s, value.getClass()));
    }
}

Topics: Java Back-end