记一次 OOM 查询过程

本贴最后更新于 1820 天前,其中的信息可能已经时移世改

现象

监控系统发现服务挂掉
登上机器 ps -ef|grep ** 发现进程还在,因为监控系统是通过心跳检测来监控服务的存活状态的,服务假死了。

排查过程

1、df、free、top 三连
磁盘空间正常、内存使用率正常、某个进程的 CPU 占用率达 300% 多

2、top -H-p pid 
查看占用 CPU 最高的进程对应线程,得到线程 ID tid

3、printf '%x' tid 
线程 ID 转为 16 进制

4、jstack pid | grep -C 5 tid
查看进程中占用 CPU 最高的线程,发现是 GC 线程

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f181001d800 nid=0x31a7 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f181001f800 nid=0x31a8 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f1810021800 nid=0x31a9 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f1810023000 nid=0x31aa runnable

5、jstat -gc pid 500
查下 GC 情况,发现几乎每秒发生一次 FULL GC

6、jmap -dump:format=b,file=/tmp/**.dump pid
dump 出 jvm 堆内存数据

7、通过 mat(Eclipse Memory Analysis Tools)分析 dump 文件
导入 dump 文件分析后,发现有两大块数据异常,占用了整个堆内存的 80% 左右

堆内存分析结果

第一块是下面 109.8MB 的这个,继续分析是 com.mysql.jdbc.JDBC4ResultSet 这个对象占用的,通过代码最终定位到问题,是一个定时任务批量处理数据时没有分页处理,一次查出了 80W+ 的数据,然后遍历处理,导致 GC 时内存不能得到释放,这里改为分页处理后,问题解决。

堆内存分析结果

第二块是 com.alibaba.druid.stat.JdbcDataSourceStat 这个对象占用了 210.3MB。

堆内存分析结果

从名字可以看出这个类是 druid 用做统计的,他会记录最近 n(默认 1000)条的 SQL 执行情况,sqlStatMap 这个 Map 是记录了最近 1000 条的 SQL 的执行情况,看看代码。

sqlStatMap = new LinkedHashMap<String, JdbcSqlStat>(16, 0.75f, false) {
    protected boolean removeEldestEntry(Map.Entry<String, JdbcSqlStat> eldest) {
        boolean remove = (size() > maxSqlSize);
        if (remove) {
            JdbcSqlStat sqlStat = eldest.getValue();
            if (sqlStat.getRunningCount() > 0 || sqlStat.getExecuteCount() > 0) {
                skipSqlCount.incrementAndGet();
            }
        }
        return remove;
    }
};

但是从图里可以看出这里其实只记录了 203 条 SQL 的执行情况,却占用了 200 多 MB,理应没有这么大的,sqlStatMap 的 key 其实就是我们执行的 SQL,继续看 sqlStatMap 里面的值,发现了几个特别大的 key,SQL 内容是这样的 update t_table set a=b where id in (id1,id2,id3....),这条 SQL 的查询条件 in 里面包含了几十万个 id,占用空间 10 多 MB,所以导致了 sqlStatMap 占用空间大。

所以这里的解决办法是:
1、先关闭 druid 的统计功能(我们并没有用到这个功能),释放 JdbcDataSourceStat 的内存占用;

<!-- 配置 p:filters="stat" 即开启druid的统计功能 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          p:filters="stat"
          p:connectionProperties="config.decrypt=true"/>

2、优化业务处理,采用分页的方式处理 。

总结

  • 大量数据一定要用分页的方式处理
  • 如非必要可以关闭 druid 的数据统计功能,可以节省大量的内存空间
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • oom
    1 引用
  • Druid
    20 引用 • 15 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...