从 OOM 看 MAT

最近新上一个服务,莫名频繁 OOM,好在启动参数配置 HeapDumpOnOutOfMemoryError,用 MAT 打开 hprof 文件,点击 Leak Suspects Report,有如下信息:

好家伙,509 个 tomcat 线程对象占据了 92+% 的内存。我们来看看其中的最大的一个实例:

看起来没有太多有用的信息,顶多是好些个局部变量指向的对象如 ArrayList$SubList 和 SearchResponse 分别消耗了 46M+ 和 35M+ 内存, 其次是 threadLocals 消耗了 27M+ 内存。

点击上面功能栏直方图 logo 进入 Histogram 界面:

按 Shallow Heap 排序,惊讶地发现 byte[] 实例占据了大约 7G+ 的内存,选中 ( 左键 ) byte[] 所在行然后右键,依次 Merge shortest Paths to GC Roots -> exclude all phantom/weak/soft ect. references 进入如下 path2gc 界面:

按 Retained Heap 排序,可以看到消耗前列的都是 TaskThread,展开 TaskThread 进一步看到是一个局部变量 ( SearchResponse ) 和 threadLocals 消耗巨大。

先看局部变量:有理由怀疑是业务代码一次性拉取大量数据导致的 OOM,但结合服务出入流量、QPS 来看并无异常,并且内存的消耗也是缓慢增加没有 ( 无法 ) 回收直至 OOM,同时结合 Heap Dump 中的 Stack Frame 和业务代码来看也没有迭代拉取数据 ( 哪怕一次性拉去大量数据,只要不是死循环,请求之后数据自然能够回收 ),因此局部变量 Pass。

重心来到 threadLocals:因为 threadLocals 的数据想要回收,需要调用 remove 删除引用,那么考虑此处存在问题,进一步展开 threadLocals,查看其中的 ThreadLocal:

点击右边界面中的灰色行,结合上图左边的 Attributes 界面中,选中 ( 左键 ) referent 所在行右键,依次 List Objects -> with incoming references,进入 inbound 界面 :

按照 Class Name 排序,可以看到这个 ThreadLocal 变量是在 skywalking 中声明的,结合源码:

typescript 复制代码
public class SessionRequestCompleteInterceptor implements InstanceMethodsAroundInterceptor {

    public static ThreadLocal<HttpContext> CONTEXT_LOCAL = new ThreadLocal<HttpContext>();

    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable {
        Object[] array = (Object[]) objInst.getSkyWalkingDynamicField();
        if (array == null || array.length == 0) {
            return;
        }
        ContextSnapshot snapshot = (ContextSnapshot) array[0];
        ContextManager.createLocalSpan("httpasyncclient/local");
        if (snapshot != null) {
            ContextManager.continued(snapshot);
        }
        CONTEXT_LOCAL.set((HttpContext) array[1]);

    }

    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable {
        return ret;
    }

    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
        Class<?>[] argumentsTypes, Throwable t) {

    }
}

en······, remove 呢?!!!

考虑到没有使用这个插件的场景,简单移除,之后没有再出现 OOM。跟之前遇到的 OOM 类似,没有限制资源使用情况,这点可以将需要 Keep 的数据序列化字节数组来控制内存消耗,避免对业务带来影响,至于功能完整上的事情,业务自行调整即可,优先保证稳定性。当时因为 OOM 的问题,还导致了数据库事务的阻塞!

相关推荐
2501_9327502613 小时前
Java IO流基础全面详解:字节流、字符流
java·开发语言
逸Y 仙X13 小时前
文章二十二:ElasticSearch EQL事件查询语言
java·大数据·elasticsearch·搜索引擎·全文检索
liulilittle13 小时前
LLAMA-CLI 运行千问3.6(R9-7945HX+64G+RTX40608G)
java·前端·llama
后端漫漫13 小时前
Redis 键值对序列化
java·redis
Lenyiin13 小时前
《LeetCode 顺序刷题》61 - 70
java·c++·python·算法·leetcode·lenyiin
敲代码的瓦龙13 小时前
Android?基础UI控件!!!
java·开发语言
Hesionberger13 小时前
LeetCode 78:子集生成全攻略
java·开发语言·数据结构·python·算法·leetcode·职场和发展
bzmK1DTbd13 小时前
Swagger API文档:Java RESTful服务的自动生成
java·开发语言·restful
G.晴天14 小时前
Linux常用命令练习流程
java·linux·运维·服务器·tomcat
身如柳絮随风扬14 小时前
Java对象在计算机中的执行原理:从JVM内存模型到对象创建全过程
java·开发语言·jvm