欢迎关注本人公众号
阅读本文前请先阅读: ThreadLocal内存泄露原因分析
线程本地变量相关的博客目录
ThreadLocal内存泄露原因分析ThreadLocal 内存泄漏 代码演示 实例演示InheritableThreadLocal:子线程继承父线程的本地变量当InheritableThreadLocal遇到线程池:主线程本地变量修改后,子线程无法读取到新值transmittable-thread-local:解决线程池之间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 {
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 {
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
;
} 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 {
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 {
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()));
}
}