Threadlocal内存泄漏

时间:2015-06-23 01:36:11

标签: java

我看到有人说:“当你想在课堂上使用ThreadLocal时,请以静态方式使用它”,例如:

private static ThreadLocal<SimpleDateFormat> dayFormat = 
    new ThreadLocal<SimpleDateFormat>() { 
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd"); 
        }
    };

我不确定为什么这可以避免内存泄漏。有人可以澄清一下吗?

1 个答案:

答案 0 :(得分:2)

我编写了一些PoC并启动jvisualvm来实际显示静态ThreadLocal是否与实例ThreadLocal有任何不同。
代码启动了10个不同的线程,每个线程运行一个亿次迭代循环,利用SimpleDateFormat字段中存储的ThreadLocal对象。

静态ThreadLocal

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " " + dayFormat.get().format(new Date()));
                for (int j = 0; j < 100000000; j++) {
                    dayFormat.get().format(new Date());
                }
                System.out.println(Thread.currentThread().getName()+" "+dayFormat.get().format(new Date()));
            }).start();
        }
    }

    private static ThreadLocal<SimpleDateFormat> dayFormat =
            new ThreadLocal<SimpleDateFormat>() {
                protected SimpleDateFormat initialValue() {
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                }
            };
}

static not null

此处没有内存泄漏,正如预期的那样。

实例ThreadLocal

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Main m = new Main();
                System.out.println(Thread.currentThread().getName() + " " + m.dayFormat.get().format(new Date()));
                for (int j = 0; j < 100000000; j++) {
                    m.dayFormat.get().format(new Date());
                }
                System.out.println(Thread.currentThread().getName()+" "+m.dayFormat.get().format(new Date()));
            }).start();
        }
    }

    private ThreadLocal<SimpleDateFormat> dayFormat =
            new ThreadLocal<SimpleDateFormat>() {
                protected SimpleDateFormat initialValue() {
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                }
            };
}

instance, not null

这里也没有内存泄漏。它的运行速度比静态版本快得多(约20%),但内存占用量差别不大。

然而,由于该代码从来没有&#34; nullify&#34; ThreadLocal对该对象的引用,我们不知道GC对这一切的感受。
以下代码可以。

静态ThreadLocal,在运行时修改

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " " + dayFormat.get().format(new Date()));
                for (int j = 0; j < 100000000; j++) {
                    dayFormat.set(null);
                    dayFormat.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                    dayFormat.get().format(new Date());
                }
                System.out.println(Thread.currentThread().getName()+" "+dayFormat.get().format(new Date()));
            }).start();
        }
    }

    private static ThreadLocal<SimpleDateFormat> dayFormat =
            new ThreadLocal<SimpleDateFormat>() {
                protected SimpleDateFormat initialValue() {
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                }
            };
}

static, nullified

在运行时修改的实例ThreadLocal

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Main m = new Main();
                System.out.println(Thread.currentThread().getName() + " " + m.dayFormat.get().format(new Date()));
                for (int j = 0; j < 100000000; j++) {
                    m.dayFormat.set(null);
                    m.dayFormat.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                    m.dayFormat.get().format(new Date());
                }
                System.out.println(Thread.currentThread().getName()+" "+m.dayFormat.get().format(new Date()));
            }).start();
        }
    }

    private ThreadLocal<SimpleDateFormat> dayFormat =
            new ThreadLocal<SimpleDateFormat>() {
                protected SimpleDateFormat initialValue() {
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                }
            };
}

instance, nullified

在这两种情况下,内存占用率再次非常相似。这一次在GC中出现了某种停顿,这导致了一个&#34;凸起&#34;在内存图中,它既出现在静态版本中,也出现在实例版本中 运行时间有点类似,这次实例版本的速度稍慢(约5%)。

因此,正如大多数人所期望的那样,如果您将ThreadLocal个对象声明为实例字段而不是静态字段,那么似乎没有任何内存泄漏风险