全量查数据库导致的OOM内存溢出

1、问题现象

在平台导入60w的人员,子系统A同时会生成对应数量用户。发现系统运行一段时间后,就会因为OOM而崩溃,由于JAVA 启动参数中设置了 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=XXX,所以可以看到有生成对应的堆快照(.hprof)文件。

2、解析堆快照

  1. 不管三七二十一,先把堆快照拿来看一下。这里使用 Eclipse的 Memory Analyzer Tool解析堆快照文件。
  2. 解析后发现,有内存溢出前的报错,定位到代码的具体位置如下:
    – 启动了一个固定周期的定时任务,检查用户有效期是否过期的判断。
// 静态代码块:
    {
        // 定时检查用户有效期,若过期推送消息
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    handleUserPeriod();
                } catch (BusinessException e) {
                    logger.info("处理用户有效期失败:{}", e.getMessage());
                }
            }
        }, 5, 30, TimeUnit.MINUTES);
    }

– 在handleUserPeriod()方法中,首先是一行:

List<User> existUserList = this.baseDao.baseOperator().selectList(new EntityWrapper<>());
... // 后面就是在内存中遍历用户,判断用户是否有效期过期的操作。

这会将所有用户都加载至内存中。至此可见,正好是这里报的OOM错误。这在用户量少的时候没有问题,但是当用户量大且JVM堆内存不足够匹配时,就会导致问题。

3、解决方案

问题:
1、一次性读取了全量的用户
2、判断用户有效期过期的操作是在内存中进行。

针对上述问题的解决办法:
1、不一次性读取全量数据,采用分批读取的办法。(时间换空间,IO更多了但也影响不大)
2、将判断用户过期的逻辑直接放在sql语句中,可以保证只读取需要的过期用户,忽略正常的用户。
3、原先使用select * 查出所有的字段,而后发现后续其实只需要用到用户“id”和“在线状态”字段,所以其实可以只读取出需要的字段,避免不必要的读取的IO消耗。

4、代码改造

根据上述思考,改进的伪代码如下:

    public void handleUserPeriod() throws BusinessException {
        logger.info("查询过期用户开始!");
        boolean needHandle = true;
        while (needHandle) {
            Date currentTime = new Date();
            // 这里为了避免OOM,限制每次最多查询 EXPIRED_USER_EACH_TIME (这里取1000)个过期用户
            List<User> existExpiredUserList = this.baseDao.getExpiredUserList(currentTime, EXPIRED_USER_EACH_TIME);
            logger.info("查询到过期用户数量:" + existExpiredUserList.size());
            handleExpiredUsers(existExpiredUserList, currentTime);

            if (existExpiredUserList.size() < EXPIRED_USER_EACH_TIME) {
                needHandle = false;
            }
        }
    }

5、添加数据验证

添加60W的用户,对比改造前和改造后的情况。发现改造前确实会OOM,而改造后的内存占用影响很小。至此大功告成。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值