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

相关推荐
程序员无隅7 分钟前
Coding Agent 为什么压缩上下文后还能继续工作?上下文模块设计拆解
java·开发语言·数据库
Python+9917 分钟前
Java 枚举类(Enum)详解:从基础到高级应用
java·开发语言·python
二炮手亮子41 分钟前
浅记java线程池
java·开发语言
一路向北North42 分钟前
Spring Security OAuth2.0(23):分布式系统授权-转发明文给微服务
java·spring·微服务
爱吃牛肉的大老虎3 小时前
rust基础之环境搭建
java·开发语言·rust
tellmewhoisi4 小时前
多版本共用redis的token有效期校验(过期重新登录)
java·redis·缓存
疯狂打码的少年4 小时前
【软件工程】结构化设计:模块独立性与耦合内聚
java·开发语言·笔记·软件工程
乐观的Terry4 小时前
3、数据库设计与领域实体
java·数据库·spring boot·spring cloud·ai编程
C++、Java和Python的菜鸟4 小时前
第4章 后端Web基础(基础知识)
java·开发语言
芷栀夏4 小时前
Java教育平台实战复盘:课程、考试与学习行为分析系统设计
java·开发语言·学习