这个补丁用于修复Hive的某些输出不会打印到beeline控制台:
https://issues.apache.org/jira/browse/HIVE-16061
定位泄漏对象
将文件dump后容易发现泄漏的对象是这个Map
测试一条查询对应的key,成功remove掉了
客户端重新连接,新增了大量key,怀疑是新建客户端连接的时候没能成功移除这些key
同时磁盘上对应的文件夹却被删除掉,可以确认是这里发生了内存泄漏。
深入分析
首先不难发现
- MAP是的私有对象,所以对它的操作一般要通过AbstractManager
- MAP是静态的,同类对象之间共享
- MAP引用不可变,不会有MAP新建替换的这种操作
但是作为remove依据的AbstractManager的name是私有的,这意味着:多个AbstractManager实现类共享着一个MAP,当自己的release()方法被调用的时候,根据自己的名字从MAP中remove掉。
从上篇分析来看,MAP中很多key没有被remove掉,是不是意味着这些AbstractManager的release()方法没被调用?
顺着这个思路看,AbstractOutputStreamAppender.stop()调用的时候会调用this.manager.release();
它的manager也是私有的,没有别的对象会操作这个,释放只发生再stop的时候
如果AbstractOutputStreamAppender的实现类被正确stop,那么manager也该被正确release(),MAP就没有这么多key;但事实相反,就表示AbstractOutputStreamAppender和AbstractManager的实现类和也被泄漏。
继续深入方法栈,可以看到具体的实现类
查看这个类的对象,可以发现实现类的确存在很多,而且应该是以前连接留下的
这个实现类是被LogUtils静态调用的,应该没有LogUtils发生泄漏。接着看方法栈会发现LogUtils是停止查询的Appender,这也能解释为什么之前查询的key能被remove掉。
目前来看只要找到连接结束时候的Appender,将其正确stop就可以解决这个问题。
省略大量断点调试的部分,找到了cleanupSessionLogDir方法
尝试修复
在HiveSessionImpl的cleanupSessionLogDir()中stop()这些对象,这样可以确保在文件无用的时候才stop()
sessionLogDir.list()可以拿到这些文件名
之前的分析已经看到LogUtils.stopQueryAppender(LogDivertAppender.QUERY_ROUTING_APPENDER, queryId)可以关闭这些
//stopQueryAppender
String[] queryIdArr = sessionLogDir.list();
for(String queryId:queryIdArr) {
LogUtils.stopQueryAppender(LogDivertAppender.QUERY_ROUTING_APPENDER, queryId);
}
提交编译,发布上线,重新测试,首次连接,发起查询来到问题出发的位置MAP.remove(this.name),发现有Map有27个kv,再次连接只有30个kv,并且只有这次会话的kv
再次测试,MAP依旧只有30个kv
但是RandomAccessFileAppender依旧有78个,三次会话分别为27、26、25个
可能是没有触发GC,需要GC后才消失。总之,至少MAP的泄漏问题得到了解决。