java finalize 方法引发的内存泄露

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);
                           }
                   }
          }
   

你可能感兴趣的:(java)