这几天与在某群与群友讨论了Runnable匿名对象导致内存泄漏的相关问题,特此记录一下。
示例代码如下:
packagecom.memleak.memleakdemo;public class Leaker {
String valueToRead= "Hello world";public voiddoSomething()
{
Thread bgThread= newThread(newRunnable()
{public voidrun() {while (true)
{
System.out.println("Running... ok");try{
Thread.sleep(1000);
}catch(InterruptedException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
);
bgThread.start();
}
}
Main函数:
packagecom.memleak.memleakdemo;/*** Hello world!
**/
public classApp
{public static voidmain( String[] args )
{
Leaker l= newLeaker();
l.doSomething();
}
}
问题出在哪?
启动此程序,main函数对应的线程在调用Leaker之后,应该退出了,后台只有一个Runnable在执行,理论上此时Leaker对象没有任何东西引用,此时应该被GC才对,但是如果使用visualVM查看下内存:
即使强制GC之后,此对象依旧存在,说明发生了泄露。
在上面图中的例子使用了一个匿名的Runnable对象,如果将此Runnable改为一个显式声明的对象,如下例子所示:
packagecom.memleak.memleakdemo;public class CauseLeakerNotToLeak implementsRunnable {public voidrun() {while (true)
{
System.out.println("Running... ok");try{
Thread.sleep(1000);
}catch(InterruptedException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
LeakerSolved.java
packagecom.memleak.memleakdemo;public classLeakerSolved {
String valueToRead= "Hello world";public voiddoSomething()
{
Thread bgThread= newThread(newCauseLeakerNotToLeak()
);
bgThread.start();
}
}
通过VisualVM则会发现已经不再泄露了:
当然,如果使用Java 8带的Lambda表达式:
packagecom.memleak.memleakdemo;public classLeakerLambda {
String valueToRead= "Hello world";public voiddoSomething()
{
Thread bgThread= new Thread(() -> {while(true) {
System.out.println("Running... ok");try{
Thread.sleep(1000);
}catch(InterruptedException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
}});
bgThread.start();
}
}
也能解决这个问题:
结论:
在创建线程的时候一定要谨慎使用匿名Runnable对象,最好使用命名对象或者Lambda表达式代替。