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

相关推荐
DCTANT20 分钟前
【原创】国产化适配-全量迁移MySQL数据到OpenGauss数据库
java·数据库·spring boot·mysql·opengauss
Touper.29 分钟前
SpringBoot -- 自动配置原理
java·spring boot·后端
黄雪超39 分钟前
JVM——函数式语法糖:如何使用Function、Stream来编写函数式程序?
java·开发语言·jvm
ThetaarSofVenice1 小时前
对象的finalization机制Test
java·开发语言·jvm
望获linux2 小时前
【实时Linux实战系列】CPU 隔离与屏蔽技术
java·linux·运维·服务器·操作系统·开源软件·嵌入式软件
JosieBook2 小时前
【Java编程动手学】使用IDEA创建第一个HelloJava程序
java·开发语言·intellij-idea
Thomas_YXQ2 小时前
Unity3D DOTS场景流式加载技术
java·开发语言·unity
summer夏1232 小时前
2025.07 做什么
java·android studio
钢铁男儿3 小时前
C# 委托(调用带引用参数的委托)
java·mysql·c#
Apex Predator3 小时前
windows安装maven环境
java·maven