java finalize 方法引發的記憶體泄露 Posted: Mon, 11 Mar 2013 java
記憶體dump
記憶體dump後,大量的記憶體(>5G) 被 java.lang.ref.Finalizer hold 住(見圖1)。 而這些記憶體是BDB佔用,懷疑是BDB有記憶體泄露(見圖2)。 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被回收。
finalizer 定位
最後我們發現 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); } } }