背景
最近生产环境一个基于 netty 的网关服务频繁 full gc
观察内存占用,并把时间维度拉的比较长,可以看到可用内存有明显的下降趋势
出现这种情况,按往常的经验,多半是内存泄露了
问题定位
找运维在生产环境 dump 了快照文件,一分析,果然不出所料,在一个 LinkedHashSet 里面, 放入 N 多的临时文件路径
可以看到,该 LinkedHashSet 是被类 DeleteOnExitHook 所引用。
DeleteOnExitHook
DeleteOnExitHook 是 jdk 提供的一个删除文件的钩子类,作用很简单,在 jvm 退出时,通过该类里面的钩子删除里面所记录的所有文件
我们简单的看下源码
class DeleteOnExitHook {
private static LinkedHashSet<String> files = new LinkedHashSet<>();
static {
// 注册钩子, runHooks 方法在 jvm 退出的时候执行
sun.misc.SharedSecrets.getJavaLangAccess()
.registerShutdownHook(2 /* Shutdown hook invocation order */,
true /* register even if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
}
private DeleteOnExitHook() {}
// 添加文件全路径到该类里面的set里
static synchronized void add(String file) {
if(files == null) {
// DeleteOnExitHook is running. Too late to add a file
throw new IllegalStateException("Shutdown in progress");
}
files.add(file);
}
static void runHooks() {
// 省略代码。。。 该方法用做删除 files 里面记录的所有文件
}
}
我们基本猜测出,在应用不断的运行过程中,不断有程序调用 DeleteOnExitHook.add
方法,放入了大量临时文件路径,导致了内存泄露
其实关于 DeleteOnExitHook
类的设计,不少人认为这个类设计不合理,并且反馈给官方,但官方觉得是合理的,不打算改这个问题
有兴趣的可以看下 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6664633
原因分析
既然已经定位到了出问题的地方,那么到底是什么情况下触发了这个 bug 了呢?
因为我们的网关是基于 netty 实现的,很快定位到了该问题是由 netty 引起的,但要说清楚这个问题并不容易