這幾天與在某群與群友討論了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表達式代替。