相信,網上很多java性能優化的帖子里都會有這么一條: 盡量把不使用的對象顯式得置為null.這樣有助於內存回收
可以明確的說,這個觀點是基本錯誤的.sun jdk遠比我們想象中的機智.完全能判斷出對象是否已經no ref..但是,我上面用的詞是"基本".也就是說,有例外的情況.這里先把這個例外情況給提出來,后續我會一點點解釋.這個例外的情況是, 方法前面中有定義大的對象,然后又跟着非常耗時的操作,且沒有觸發JIT編譯..總結這句話,就是: 除非在一個方法中,定義了一個非常大的對象,並且在后面又跟着一段非常耗時的操作.並且,該方法沒有滿足JIT編譯條件,否則顯式得設置 obj = null是完全沒有必要的
上面這句話有點繞,但是,上面說的每一個條件都是有意義的.這些條件分別是
1 同一個方法中 2 定義了一個大對象(小對象沒有意義) 3 之后跟着一個非常耗時的操作. 4 沒有滿足JIT編譯條件
上面4個條件缺一不可,把obj顯式設置成null才是有意義的. 下面我會一一解釋上面的這些條件
同一個方法中
這個條件是最容易理解的,如果大對象定義在其他方法中,那么是不需要設置成Null的,
1 public class Test 2 { 3 4 public static void main(String[] args){ 5 6 foo(); 7 8 System.gc(); 9 } 10 11 public static void foo(){ 12 byte[] placeholder = new byte[64*1024*1024]; 13 } 14 }
對應的輸出如下,可以看到64M的內存已經被回收
D:\>java -verbose:gc Test [GC 66798K->66120K(120960K), 0.0012225 secs] [Full GC 66120K->481K(120960K), 0.0059647 secs]
其實很好理解,placeholder是foo方法的局部變量,在main方法中調用的時候,其實foo方法對應的棧幀已經結束.那么placeholder指向的大對象自然被gc的時候回收了.
定義了一個大對象
這句話的意思也很好理解.只有定義的是大的對象,我們才需要關心他盡快被回收.如果你只是定義了一個 Integer i = new Integer(1); 后續手動設置成null讓gc回收是沒有任何意義的.
后面跟着一個非常耗時的操作
這里理解是:后面的這個耗時的可能超過了一個GC的周期.例如
1 public static void main(String[] args) throws Exception{ 2 byte[] placeholder = new byte[64*1024*1024]; 3 Thread.sleep(3000l); 4 // dosomething 5 }
在線程sleep的三秒內,可能jvm已經進行了好幾次ygc.但是由於placeholder一直持有這個大對象,所以造成這個64M的大對象一直無法被回收,甚至有可能造成了滿足進入old 區的條件.這個時候,在sleep之前,顯式得把placeholder設置成Null是有意義的. 但是,如果沒有這個耗時的操作,main方法可以非常快速的執行結束,方法返回,同時也會銷毀對應的棧幀.那么就是回到第一個條件,方法已經執行結束,在下一次gc的時候,自然就會把對應的"垃圾"給回收掉.
沒有滿足JIT編譯條件
jit編譯的觸發條件,這里就不多闡述了.對應的測試代碼和前面一樣
1 public class Test 2 { 3 public static void main(String[] args) throws Exception{ 4 byte[] placeholder = new byte[64*1024*1024]; 5 placeholder = null; 6 //do some time-consuming operation 7 System.gc(); 8 } 9 }
在解釋執行中,我們認為placeholder = null;是有助於對這個大對象的回收的.在JIT編譯下,JIT編譯器進行控制流和數據流分析后,生成的OopMap就提供比較精確的信息,不需要通過”=null”來告知對象使命已經完成.退一步說,這時即使有”=null”操作,也會被優化掉,生成出來的本地代碼與沒有”=null”操作的版本是一模一樣的.