歡迎關注本人公衆號
閱讀本文前請先閱讀: 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後,對內存全部釋放,未發生內存泄漏。