从 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 的问题,还导致了数据库事务的阻塞!

相关推荐
Mr_sun.几秒前
Day06——权限认证-项目集成
java
瑶山3 分钟前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
abluckyboy10 分钟前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法
2301_8187320612 分钟前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
2501_9419820525 分钟前
深度对比:Java、Go、Python 实现企微外部群推送,哪个效率更高?
java·golang·企业微信
马猴烧酒.1 小时前
【面试八股|JAVA多线程】JAVA多线程常考面试题详解
java·服务器·数据库
sino爱学习1 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端
风生u2 小时前
activiti7 详解
java
岁岁种桃花儿2 小时前
SpringCloud从入门到上天:Nacos做微服务注册中心(二)
java·spring cloud·微服务
Word码2 小时前
[C++语法] 继承 (用法详解)
java·jvm·c++