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

相关推荐
疯狂成瘾者14 小时前
LangChain4j ApacheTikaDocumentParser:多格式文档接入的统一入
java·langchain4j
庞轩px15 小时前
第三篇:泛型深度解析——类型擦除与通配符的奥秘
java·编译·泛型·类型擦除
HoneyMoose1 天前
Jenkins Cloudflare 部署提示错误
java·servlet·jenkins
阿丰资源1 天前
基于SpringBoot的物流信息管理系统设计与实现(附资料)
java·spring boot·后端
Predestination王瀞潞1 天前
Java EE3-我独自整合(第四章:Spring bean标签的常见配置)
java·spring·java-ee
overmind1 天前
oeasy Python 121[专业选修]列表_多维列表运算_列表相加_列表相乘
java·windows·python
资深数据库专家1 天前
总账EBS 应用服务器1 的监控分析
java·网络·数据库
房开民1 天前
可变参数模板
java·开发语言·算法
t***5441 天前
如何在现代C++中更有效地应用这些模式
java·开发语言·c++
_深海凉_1 天前
LeetCode热题100-最小栈
java·数据结构·leetcode