一次生产内存溢出记录

一,报错信息:

二,问题定位:这是一个查询功能

三,代码:

public JSONObject selectInvTransactions(InvTransactionsVO invTransactionsVO) {  
        int pageNum = invTransactionsVO.getPageNum() == null ? 0 : invTransactionsVO.getPageNum();
        int pageRows = invTransactionsVO.getPageRows() == null ? 0 : invTransactionsVO.getPageRows();        
        if (pageNum != 0 && pageRows != 0) {
            PageHelper.startPage(pageNum, pageRows);
        }
        List<InvTransactionsEntity> list = invTransactionsMapper.selectInvTransactions(invTransactionsVO);
        List<InvTransactionsVO> listVO = new ArrayList<InvTransactionsVO>();
        for (InvTransactionsEntity invTransactionsEntity : list) {
            InvTransactionsVO invTransactions = new InvTransactionsVO();
			BeanUtil.getInstance().copyNotNull(invTransactionsEntity, invTransactions);
			Double a = invTransactionsEntity.getfQty() == null ? 0.000 : invTransactionsEntity.getfQty();
			Double b = invTransactionsEntity.gettQty() == null ? 0.000 : invTransactionsEntity.gettQty();
            invTransactions.setInvTransQty(Math.abs(new BigDecimal(a.toString()).subtract(new BigDecimal(b.toString())).doubleValue()));
            // 变化前库存状态名称
            Map<String, Object> fStatusNameMap = new HashMap<>();
            fStatusNameMap.put("dictValue", invTransactions.getfStatus());
            fStatusNameMap.put("code", DictTypeE.WMS_INVENTORY_TYPE.getTypeCode());// 库存状态
            fStatusNameMap.put("factoryAreaId", invTransactionsVO.getAreaId());
            String fStatusName = hystrixWrappedAuthServiceClient.valueToKey(fStatusNameMap);
            invTransactions.setfStatusName(fStatusName);
            // 变化后库存状态名称
            Map<String, Object> tStatusNameMap = new HashMap<>();
            tStatusNameMap.put("dictValue", invTransactions.gettStatus());
            tStatusNameMap.put("code", DictTypeE.WMS_INVENTORY_TYPE.getTypeCode());// 库存状态
            tStatusNameMap.put("factoryAreaId", invTransactionsVO.getAreaId());
            String tStatusName = hystrixWrappedAuthServiceClient.valueToKey(tStatusNameMap);
            invTransactions.settStatusName(tStatusName);
            // 变化前零件状态名称
            Map<String, Object> fItemStatusNameMap = new HashMap<>();
            fItemStatusNameMap.put("dictValue", invTransactions.getfItemStatus());
            fItemStatusNameMap.put("code", DictTypeE.WMS_ITEM_INV_TYPE.getTypeCode());// 零件库存状态
            fItemStatusNameMap.put("factoryAreaId", invTransactionsVO.getAreaId());
            String fItemStatusName = hystrixWrappedAuthServiceClient.valueToKey(fItemStatusNameMap);
            invTransactions.setfItemStatusName(fItemStatusName);
            // 变化后零件状态名称
            Map<String, Object> tItemStatusNameMap = new HashMap<>();
            tItemStatusNameMap.put("dictValue", invTransactions.gettItemStatus());
            tItemStatusNameMap.put("code", DictTypeE.WMS_ITEM_INV_TYPE.getTypeCode());// 零件库存状态
            tItemStatusNameMap.put("factoryAreaId", invTransactionsVO.getAreaId());
            String tItemStatusName = hystrixWrappedAuthServiceClient.valueToKey(tItemStatusNameMap);
            invTransactions.settItemStatusName(tItemStatusName);
            
            // 交易类型名称
            Map<String, Object> operateTypeNameMap = new HashMap<>();
            operateTypeNameMap.put("dictValue", invTransactions.getOperateType());
            operateTypeNameMap.put("code", DictTypeE.WMS_INV_TRANS_TYPE.getTypeCode());// 交易类型
            operateTypeNameMap.put("factoryAreaId", invTransactionsVO.getAreaId());
            String operateTypeName = hystrixWrappedAuthServiceClient.valueToKey(operateTypeNameMap);
            invTransactions.setOperateTypeName(operateTypeName);
            invTransactions.setfMuLpNo(invTransactionsEntity.getfMuLpNo());
            invTransactions.settMuLpNo(invTransactionsEntity.gettMuLpNo());
            SupplierVO supplierVO = hystrixWrappedBizServiceClient.feignSelectById(invTransactionsEntity.getSupplierId());
            if (supplierVO != null) {
                invTransactions.setSupplierCode(supplierVO.getCode());
            }
            listVO.add(invTransactions);
        }
        PageInfo<InvTransactionsEntity> infoEntity = new PageInfo<InvTransactionsEntity>(list);
        PageInfo<InvTransactionsVO> info = new PageInfo<InvTransactionsVO>(listVO);
        List<InvTransactionsVO> invTransactionsList = info.getList();
        JSONObject json = new JSONObject();
        json.put("result", invTransactionsList);
        json.put("total", infoEntity.getTotal());
        return json;
    }

四:排查方向:

1.应用服务器提示错误的解决:
把启动参数内存值设置足够大。

2.Java代码导致错误的解决:
重点排查以下几点:

1)检查代码中是否有死循环或递归调用。

2)检查是否有大循环重复产生新对象实体。

3)检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询

4 )检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

扫描二维码关注公众号,回复: 5322957 查看本文章

五:实际分析:

1,JVM堆内存设置过小,有可能

2,该该方法中存在for循环,且循环中大量创建对象,可能

3,一次查询过多数据,已分页,排除

4,List未释放,一般都不释放,不太可能

六,实际定位:

1,查询GC日志:

日志内容:

但此时只知道该时间点发生GC了,并不能直接定位到是具体哪里导致内存溢出。是可以该时间点再去查看日志文件,查找执行哪一个方法时发生内存溢出,以此来定位方法。其实日志文件报错信息中已经抛出了哪个方法,也无需根据gc文件定位出错方法。

2,

查询服务进程:ps -ef|grep java

查看服务内存信息:

通过以上信息可以看到,堆内存分配大小约为3G,整个内存大小为15G。

3,循环中创建了大量对象,可以优化这个地方,上面查询返回vo,循环中不创建对象。

七,思考误区,问题准确定位

之前分析:从报错信息看,报的是查询路径,推断是查询导致的,内存溢出。然后分析查询方法,因为用的分页查询,每次查询的只是一页的十条内容,for循环中也是十条,所以在里面创建对象也不会导致内存溢出,量小。点击下一页,重新掉、调用方法,对象会被回收。

误区:当时怀疑是导出导致的,因为导出数据比较多,达20万以上。但是抛出异常的地址,是查询,不是导出,就排除了这个原因,而且在本地测试导出的时候也没有坚持导完看结果,就没定位到问题。实际上,导出也调用了查询方法,抛出查询地址当然正常。

实际定位:还是把生产的大量数据导入测试环境库,测试导出,大概20分钟后,问题重现了,测试服务挂了,日志错误与生产出现的一致,由此定位到为题。

原因:导出的数据量很大,方法未结束,for循环中创建对象无法被释放。

优化:查询返回vo,避免在循环中创建对象。

八,举一反三,总结

1,类似问题如何解决:从报错信息,定位方法;把生产数据拉到测试环境测试;用过top,jmap,查看内存变化,对象数量变化。

2,不要把问题想的这么绝对,多想一步。

3,动手,实践,动手,实践,动手,实践。

猜你喜欢

转载自blog.csdn.net/C18298182575/article/details/87348250
今日推荐