阅读 432

记一次临近上线程序发生OOM

记一次临近上线程序发生OOM

故事背景

最近一直在赶着应用上线,基本已经封包准备上线了,谁都不想在这时间点上出差错~

当时应用已经上线pre,压力测试已经通过,然而昨天下午测试组的同事突然找到我,说我的应用没有消费kafka的数据,其他应用都已经同步消费了,搞得我一脸懵逼.

首先先上Consul上看服务的情况.发现pre上面除了我的应用,其他的都还健在!!!!(心里有点方~)

赶紧连上服务器查看应用,发现日志在停留在19号凌晨1点多.

[2019-07-19 01:46:33] [WARN ] [dc16dc8a-79d8-4ec5-adca-3f7bdff7bb7d] [http-nio-6030-exec-7] [AbstractHandlerExceptionResolver.java:140] [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver] : Resolved [org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded]
复制代码

然后赶紧重启让测试同事去验证数据同步~

排查问题

由于服务器内存吃紧(8G),然后又需要部署好多个应用,默认启动JVM参数设置得比较低(-Xms256m -Xmx512m -Xmn128m -XX:MaxPermSize=64m)~

由于没有加上OOM dump参数,所以那时候也没办法去定位问题,只能是在本地将问题复现了.

  1. 首先在本地设置同样的JVM参数,再加上发生OOM dump的路径
-Xms256m -Xmx512m -Xmn128m -XX:MaxPermSize=64m -XX:ErrorFile=G:/heap/dump/hs_err_pid%p.log  -XX:HeapDumpPath=G:/heap/dump -XX:+HeapDumpOnOutOfMemoryError
复制代码

然后让程序跑一段时间,程序果真复现了!!!同时在我们设置好的发生OOM生成对应的堆文件.

[2019-07-20 11:01:50] [ERROR] [c8dc279e-1e7e-4a1a-b646-0203f23e7ba6] [http-nio-6030-exec-116] [AdviceController.java:48] [com.ost.micro.scheduler.strategy.controller.AdviceController] : {}
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1006)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)
复制代码

让我们看一下现在的JVM堆图:

JVM堆

堆内存一直不回收,导致了OOM.

打开Eclipse Memory Analyzer 分析生成的dump文件.我们发现org.drools.core.impl.KnowledgeBaseImpl占据了72%!!!

分析

解决问题

工作原因,接触到了drools这个规则引擎,上面发现的问题明显就是使用drools整出来的,由于程序里面KieBase设置的是单例,那么问题应该不是出在它身上。

先来看一下使用drools的代码~

public <T extends IRule> Integer execute(T t) {
    KieSession session = this.session();
    session.insert(t);
    int size = session.fireAllRules();
    if (Objects.nonNull(t.getIError())){
        throw new PayStrategyException(t.getIError());
    }
    return size;
}
复制代码

此时我就在怀疑,是不是KieSession没关闭导致的问题?看了一下获取KieSession的描述~

/**
 * Creates a new {@link KieSession} using the default session configuration.
 * Don't forget to {@link KieSession#dispose()} session when you are done.
 *
 * @return created {@link KieSession}
 */
复制代码

写的很清楚,当执行完了之后,必须执行dispose()方法回收该session.修改代码如下

public <T extends IRule> Integer execute(T t) {
    KieSession session = null;
    try {
        session = this.session();
        session.insert(t);
        int size = session.fireAllRules();
        if (Objects.nonNull(t.getIError())) {
            throw new PayStrategyException(t.getIError());
        }
        return size;
    } finally {
        if (Objects.nonNull(session)) {
            session.dispose();
        }
    }
}
复制代码

设置一下JVM参数(-Xmx1344M -Xms1344M -Xmn448M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:+CMSClassUnloadingEnabled -XX:+ParallelRefProcEnabled -XX:+CMSScavengeBeforeRemark), 重新跑一下JVM堆如下图:

JVM堆

写在最后

由于对Drools这个工作引擎不熟悉,所以差点搞出问题,看来还是要多看看官方的文档,记下此次事故提醒自己~

最后推荐一下生成JVM参数的网址http://xxfox.perfma.com/jvm/generate

关注下面的标签,发现更多相似文章
评论