ThreadLocal 内存泄漏 代码演示 实例演示

欢迎关注本人公众号

在这里插入图片描述
阅读本文前请先阅读: ThreadLocal内存泄露原因分析

线程本地变量相关的博客目录

不使用ThreadLocal

下面这段程序创建了一个有5个线程的线程池。
每个线程致性都申请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 {//总共有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("开始执行");
                });
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

使用JDK自带的VisualVM来观察对内存占用情况,下图中锯齿状的蓝色区域是堆已经使用的空间大小,可以看到在0-70内,这是因为每个线程都会申请5M空间,过一小段时间后,就会触发一次youngGC, 内存就会释放。
在19:30:36处我手动触发了一次GC ,可以看到堆空间基本都释放。
说明LocalVariable全都释放,未发生内存泄漏。
在这里插入图片描述

使用ThreadLocal,但不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 {//总共有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("开始执行");
                });
                Thread.sleep(100);
            }            
            local = null;//这里设置为null,依旧会造成内存泄漏
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面代码中定义了static的ThreadLocal变量local, 但是当for循环致性完毕后,又将local设置为null。普通对象,此时就没有强引用了,当GC时就会被回收掉。
但是通过下面图可以看到,即使for循环结束后手动触发了GC,堆内存空间依旧占用约25MB空间,正好是线程池中5个线程的LocalVariable对象的空间和。
所以发生了内存泄漏。
发生内存泄漏的原因见 ThreadLocal内存泄露原因分析
在这里插入图片描述

使用Thread Local,且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 {//总共有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("开始执行");
                    local.remove();
                });
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面代码中,线程致性完成后,都调用了local.remove()来将threadLocal内的对象删除。下图中可以看到在手动触发GC后,对内存全部释放,未发生内存泄漏。

在这里插入图片描述

单线程演示内存泄漏

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

    static class LocalVariable {//总共有50M
        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("开始执行");
            Thread.sleep(100);

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

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

从结果中可以看到,localVariable的50MB空间释放了,但是ThreadLocal中存放的50MB空间没有释放。
在这里插入图片描述

引用

像下面代码,stu是再for外面定义的,线程内每次都使用这个stu对象,那么依旧会有线程安全问题,因为该stu对象还是多线程之间共享这个对象。
在这里插入图片描述
所以一定要再每个线程内new对象,避免多线程共享。
也可以自己实现MyThreadLocal,来手动复制对象,避免复用同一个对象

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

©️2020 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值