内存dump后,大量的内存(>5G) 被 java.lang.ref.Finalizer hold 住(见图1)。 而这些内存是BDB占用,怀疑是BDB有内存泄露(见图2)。
为什么会是 java.lang.ref.Finalizer 的引用导致BDB无法释放内存?没有开启实时索引的机器上BDB不会有内存泄露?重新了解java finalize的机制:
实现了finalize的对象,创建和回收的过程都更耗时。创建时,会新建一个额外的Finalizer 对象指向新创建的对象。 而回收时,至少需要经过两次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);
}
}
检查mandy中的代码发现,有个shutdown 方法被调用两次一次显示调用,一次是finalize中调用第一次调用时,执行上面的代码即便timetowait为0,也没问题,会有线程唤醒调用者; 随后唤醒线程也退出.第二次finalize线程调用时,执行上面的代码timetowait为0时,调用线程(finalize线程)会一直block。因为唤醒线程在第一 次调用时已经退出.
去掉finalize 方法即可
执行下面的程序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);
}
}
}