1. 程式人生 > >java finalize 方法引發的記憶體洩露

java finalize 方法引發的記憶體洩露

Posted: Mon, 11 Mar 2013

記憶體dump

記憶體dump後,大量的記憶體(>5G) 被 java.lang.ref.Finalizer hold 住(見圖1)。 而這些記憶體是BDB佔用,懷疑是BDB有記憶體洩露(見圖2)。

  • Drawing
  • Drawing

java finalizer 機制

為什麼會是 java.lang.ref.Finalizer 的引用導致BDB無法釋放記憶體?沒有開啟實時索引的機器上BDB不會有記憶體洩露?重新瞭解java finalize的機制:

實現了finalize的物件,建立和回收的過程都更耗時。建立時,會新建一個額外的Finalizer 物件指向新建立的物件。 而回收時,至少需要經過兩次GC.

  • 第一次GC, 檢測到物件只有被Finalizer引用,將這個物件放入 java.lang.ref.Finalizer.ReferenceQueue 此時,因為Finalizer的引用,物件還無法被GC.
  • java.lang.ref.Finalizer$FinalizerThread 會不停的清理Queue的物件,remove掉當前元素,並執行物件的finalize方法。
  • 清理後,物件沒有任何引用,在下一次GC被回收。

定位

最後我們發現 java.lang.ref.Finalizer$FinalizerThread wait在一個實時索引的執行緒上, 見下面程式碼.

//   java多執行緒同步的一種典型實現:
   //  迴圈檢查條件(cachedreaderTimestamp <= begintime), 每次wait一小段時間(200ms),最多wait指定的時間(timeout)
   //  另外一個執行緒會修改條件,讓條件為true (cachedreaderTimestamp > begintime)
   while (cachedreaderTimestamp <= begintime)
   {
           synchronized (cachemonitor)
               {
                    cachemonitor.notifyAll();
                   long elapsed = System.currentTimeMillis() - begintime;
                   if (elapsed > timeout) // elapsed 有可能等於 timeout
               {
                             log.debug("refreshCached reader timeout in " + elapsed + "ms");
                             throw new ZoieException("refreshCached reader timeout in " + elapsed + "ms");
               }
                   long timetowait = Math.min(timeout - elapsed, 200);
           try
           {
                 cachemonitor.wait(timetowait); //要麼被其他執行緒喚醒,要麼等待timetowait;如果timetowait為0時,只能被 其他執行緒喚醒,否則一直等待
           }    catch (InterruptedException e)
           {
                 log.warn("refreshCache", e);
           }
   }
   

code review

檢查mandy中的程式碼發現,有個shutdown 方法被呼叫兩次一次顯示呼叫,一次是finalize中呼叫第一次呼叫時,執行上面的程式碼即便timetowait為0,也沒問題,會有執行緒喚醒呼叫者; 隨後喚醒執行緒也退出.第二次finalize執行緒呼叫時,執行上面的程式碼timetowait為0時,呼叫執行緒(finalize執行緒)會一直block。因為喚醒執行緒在第一 次呼叫時已經退出.

fix

去掉finalize 方法即可

replay

執行下面的程式java -Xmx100m Finalize 程式永不停止

public class Finalize {
          byte[] a = new byte[10 * 1024 * 1024 ];
   
          protected void _finalize() {
                   synchronized(this) {
                           try {
                                   this.wait(0);
                           } catch(Exception e) {
                                   System.err.println(e);
                           }
                   }
          }
   
          public static void main(String[] args) throws Exception {
                   while(true) {
                           new Finalize();
                           Thread.sleep(200);
                   }
          }
   }
   

修改後,實現 finalize 方法java -Xmx100m Finalize

很快報記憶體溢位錯誤Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

protected void _finalize() {
                   synchronized(this) {
                           try {
                                   this.wait(0);
                           } catch(Exception e) {
                                   System.err.println(e);
                           }
                   }
          }
   
   --->
   
   protected void finalize() {
                   synchronized(this) {
                           try {
                                   this.wait(0);
                           } catch(Exception e) {
                                   System.err.println(e);
                           }
                   }
          }