记一次真实的线上OOM

背景

晚上快下班,前端告诉我有一个接口迟迟没有响应,可这个项目很久没提过新东西了,更不要说上线了。

排查思路

于是先去登到机器上top,top -Hp一下看下CPU情况,好像并没有什么异常。 然后再free -h看内存,发现确实服务器内存剩余空间不足500MB,那么大概率是内存泄露的问题。 那么直接看下jvm gc情况:

shell 复制代码
jstat -gcutil 38714 
S0    S1    E      O     M    CCS   YGC  YGCT   FGC     FGCT       GCT 
0.00 0.00 99.99 100.00 88.26 84.01 4494 48.648 127745 30434.755 30483.404
  • 那么问题显而易见
    • Old 区 100%,Eden 区 99.99%老年代和Eden区全部被占满,对象无法分配,也无法回收。
    • FGC = 127745,FGCT = 30434.755 秒Full GC 发生了 12 万次 ,累计耗时 8.45 小时
    • S0/S1 为 0 :因为 Old 区满了,对象从 Young 区晋升不上去,Survivor 区也无法正常工作

确实是很严重的线上问题了,那么立刻通过 jmap 命令 dump内存快照。

shell 复制代码
jmap -dump:format=b,file=heap.hprof 38714

然后重启服务,保证线上服务是可用的。 本地通过MAT (Eclipse Memory Analyzer) 分析dump文件 打开dump文件可以发现:

可疑问题对象占了1.5GB堆内存

Leak Suspects

堆内 jcifs.context.BaseContext 对象实例有19,575个,并且占了87.18%的内存,那这一定是不合理的。 再看 Common Path To the Accumulation Point

确实问题点都出现在这里。

那么再去看业务代码,看看哪里导致了OOM。

java 复制代码
public static List<TreeNode> getPackage() {
        java.util.Properties props = new java.util.Properties();
        props.put("jcifs.smb.client.minVersion", "SMB202");
        props.put("jcifs.smb.client.maxVersion", "SMB311");
        props.put("jcifs.smb.client.disableSMB1", "true");
        props.put("jcifs.smb.client.encrypt", "true");

        CIFSContext baseContext = new BaseContext(new PropertyConfiguration(props));
        NtlmPasswordAuthentication authentication = new NtlmPasswordAuthentication(baseContext, packageIP, packageUser, packagePsw);
        CIFSContext authContext = baseContext.withCredentials(authentication);
        SmbFile remoteFile = new SmbFile(packagePath, authContext);
        ...
}

每次调用此方法都会创建一个BaseContext对象,并且最终没有做close,那么有可能是这里出现了问题。刚好注意到jcifs库有这样一个issue

大概意思是SmbFileConnection类的构造器会创建一个jcifs.context.BaseContext对象,jcifs.context.BaseContext对象创建时会调用其超类的AbstractCIFSContext的构造方法,AbstractCIFSContext的构造方法中会为jcifs.context.BaseContext对象注册一个shutdown hookSmbFileConnection对象使用结束,会被垃圾回收器回收掉,但是jcifs.context.BaseContext对象还会存在一个shutdown hook的引用链导致不能被回收。

issue中的问题在jcifs 3.9.0版本仍然存在,我们项目中使用的jcifs版本是2.1.7,为了保险,再看一下2.1.7版本的jcifs怎么实现的。

java 复制代码
    public abstract class AbstractCIFSContext extends Thread implements CIFSContext {
        private static final Logger log = LoggerFactory.getLogger(AbstractCIFSContext.class);
        private boolean closed;

        public AbstractCIFSContext() {
            Runtime.getRuntime().addShutdownHook(this);
        }
    ...
    }

确实还是有此问题的,那么就可以着手改代码了。

解决问题

其实显示调用close()方法是可以解决此问题的,贴上源码:

java 复制代码
    public boolean close() throws CIFSException {
        if (!this.closed) {
            Runtime.getRuntime().removeShutdownHook(this);
        }

        return false;
    }

但是为了防止频繁的创建销毁对象,我选择把此对象做成单例的

java 复制代码
private static CIFSContext getBaseContext() {
    if (baseContext == null) {
        synchronized (PackageBranchUtil.class) {
            java.util.Properties props = new java.util.Properties();
            props.put("jcifs.smb.client.minVersion", "SMB202");
            props.put("jcifs.smb.client.maxVersion", "SMB311");
            props.put("jcifs.smb.client.disableSMB1", "true");
            props.put("jcifs.smb.client.encrypt", "true");
            try {
                baseContext = new BaseContext(new PropertyConfiguration(props));
            } catch (jcifs.CIFSException e) {
                log.error("Failed to initialize CIFS context: {}", e.getMessage());
                throw new RuntimeException("Failed to initialize CIFS context", e);
            }
        }
    }
    return baseContext;
}

验证

先压测一把

再次dump,查看堆区内存

可以看到此接口不会导致jcifs.context.BaseContext对象泛滥的情况了,搞定!

如需转载,请私信联系博客作者

相关推荐
Flittly16 小时前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了17 小时前
Java 生成二维码解决方案
java·后端
人活一口气21 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP1 天前
Vibe Coding -- 完整项目案例实操
java
荣码1 天前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing1 天前
Google第三方授权登录
java·后端·程序员
明月光8181 天前
从一行 @Builder 说起:重新拾起 Java 的 Lombok、注解与 Builder 模式
java
考虑考虑1 天前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯1 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
青石路2 天前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java