Java匿名对象导致的内存泄漏

这几天与在某群与群友讨论了Runnable匿名对象导致内存泄漏的相关问题,特此记录一下。

 

示例代码如下:

package com.memleak.memleakdemo;

public class Leaker {
    
    String valueToRead = "Hello world";
    
    public void doSomething()
    {
        Thread bgThread = new Thread(
            new Runnable()
            {

                public void run() {
                    while (true)
                    {
                        System.out.println("Running... ok");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
                
            }
        );
        bgThread.start();
    }

}

Main函数:

package com.memleak.memleakdemo;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        Leaker l = new Leaker();
        l.doSomething();
    }
}

问题出在哪?

启动此程序,main函数对应的线程在调用Leaker之后,应该退出了,后台只有一个Runnable在执行,理论上此时Leaker对象没有任何东西引用,此时应该被GC才对,但是如果使用visualVM查看下内存:

即使强制GC之后,此对象依旧存在,说明发生了泄露。

 

在上面图中的例子使用了一个匿名的Runnable对象,如果将此Runnable改为一个显式声明的对象,如下例子所示:

package com.memleak.memleakdemo;

public class CauseLeakerNotToLeak implements Runnable {

    public void run() {
        while (true)
        {
            System.out.println("Running... ok");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}
LeakerSolved.java
package com.memleak.memleakdemo;

public class LeakerSolved {
    
    String valueToRead = "Hello world";
    
    public void doSomething()
    {
        Thread bgThread = new Thread(
            new CauseLeakerNotToLeak()
        );
        bgThread.start();
    }

}

通过VisualVM则会发现已经不再泄露了:

 

当然,如果使用Java 8带的Lambda表达式:

package com.memleak.memleakdemo;

public class LeakerLambda {
    
    String valueToRead = "Hello world";
    
    public void doSomething()
    {
        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表达式代替。

posted on 2020-02-25 00:31  specialist  阅读(1048)  评论(0编辑  收藏  举报