美文网首页
web服务挂了 java.lang.OutOfMemoryErr

web服务挂了 java.lang.OutOfMemoryErr

作者: 一只浩子 | 来源:发表于2023-06-30 21:11 被阅读0次
web服务挂了:排查问题,访问服务web端出现502,前端vue项目能正常访问,说明nginx服务没挂,后端服务可能挂了,打开日志打印,没有打印了,确定是服务挂了。
一、查看日志文件,这个接口多次出现gc

Url:/collect/import/area
Failed to complete processing of a request java.lang.OutOfMemoryError: GC overhead limit exceeded

  1. 检查代码,调用频繁的接口,是否存在对象未释放,导致内存泄漏;(调用量大的是excel导入接口,下载excel数据接口)
  2. 线上抓包,利用Mat内存解析工具,排查是否存在大对象;
查看代码
  1. 导入excel接口/collect/import/area,excel文件解析,使用了poi包workbook对象,未进行释放;
public TcoCollectExcelMsgDto analysisExcelFileToList(File file) {
        checkExcelFile(multipartFile);
        InputStream in = null;
        Workbook workbook = null;
        try {
            in = new FileInputStream(file);
            workbook = WorkbookFactory.create(in);
        } catch (Exception e) {
            AssertU.isTrue(false, "文件读取失败", e);
        } finally {
            Tools.release(in);
        }

        return analysis(workbook);
    }

修改为

public TcoCollectExcelMsgDto analysisMultipartFileToList(MultipartFile multipartFile) {
        checkExcelFile(multipartFile);
        TcoCollectExcelMsgDto result = null;
        InputStream in = null;
        Workbook workbook = null;
        try {
            in = multipartFile.getInputStream();
            workbook = WorkbookFactory.create(in);
            result = analysis(workbook);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            Tools.release(workbook, in);
        }
        return result;
    }

也可以是这种写法,只需要把要关闭资源的代码放入try语句中即可,JDK8开始已经不需要我们再手动关闭资源。

/**
 * 解析前端传入的excel表格中数据
 * @param multipartFile
 */
public TcoCollectExcelMsgDto analysisMultipartFileToList(MultipartFile multipartFile) {
    checkExcelFile(multipartFile);
    TcoCollectExcelMsgDto result = null;
    try (InputStream in = multipartFile.getInputStream();
         Workbook workbook = WorkbookFactory.create(in)) {
        result = analysis(workbook);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
    }
    return result;
}
  1. 下载文件,new文件流,没有trycatch,也可能会存在未进行释放风险;
public String realOssPathByTemplate(String fileName, String templateName, List<?> dataList){
        //创建流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        //从oss读取模板文件
        InputStream inputStream = ossClientUtil.downloadFile(templateName);
        ExcelWriter excelWriter = EasyExcel
                .write(bos)
                .withTemplate(inputStream)
                .build();
        WriteSheet writeSheet = EasyExcel.writerSheet(0).build();

        excelWriter.write(dataList, writeSheet);
        excelWriter.finish();
        excelWriter.close();
        release(inputStream);
        return getOssPath(bos,fileName);
    }

优化后

public String realOssPathByTemplate(String fileName, String templateName, List<?> dataList){
        ByteArrayOutputStream bos = null;
        InputStream inputStream = null;
        ExcelWriter excelWriter = null;
        String ossPath = null;
        try {
            //创建流
            bos = new ByteArrayOutputStream();
            //从oss读取模板文件
            inputStream = ossClientUtil.downloadFile(templateName);
            excelWriter = EasyExcel
                    .write(bos)
                    .withTemplate(inputStream)
                    .build();
            WriteSheet writeSheet = EasyExcel.writerSheet(0).build();

            excelWriter.write(dataList, writeSheet).finish();
            ossPath = getOssPath(bos, fileName);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            excelWriter.close();
            release(inputStream);
        }
        return ossPath;
    }
  1. 下载文件,存在个别new文件流,未进行释放;
@ApiOperation("错误信息导出”)
    @GetMapping("/export/errorinfo”)
    public R errorInfo(String guid) {
        if (StringUtils.isBlank(guid)) {
            AssertU.isTrue(false, "guid不能为空!”);
        }
        //从redis中取出错误信息
        List<String> msgList = redisUtil.get(StaticValue.DECLARE_ERROR_PREFIX + ":" + guid, List.class);

        if (CollectionUtils.isEmpty(msgList)) {
            AssertU.isTrue(true, "暂无错误信息!”);
        }
        List<DeclareTmpMsgData> msgDataList = new ArrayList<>();
        for (String msg : msgList) {
            DeclareTmpMsgData msgData = new DeclareTmpMsgData();
            msgData.setMsg(msg);
            msgDataList.add(msgData);
        }
        String fileName = "基地档案导入报错提示_" + DateTime.getCurrentDate_YYYYMMDD() + ".xlsx”;
        //创建流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        EasyExcel.write(new BufferedOutputStream(bos), DeclareTmpMsgData.class).sheet("errorinfo").doWrite(msgDataList);
        return R.data(getOssPath(bos, fileName));
    }

优化后

@ApiOperation("错误信息导出”)
    @GetMapping("/export/errorinfo”)
    public R errorInfo(String guid) {
        if (StringUtils.isBlank(guid)) {
            AssertU.isTrue(false, "guid不能为空!”);
        }
        //从redis中取出错误信息
        List<String> msgList = redisUtil.get(StaticValue.DECLARE_ERROR_PREFIX + ":" + guid, List.class);

        if (CollectionUtils.isEmpty(msgList)) {
            AssertU.isTrue(true, "暂无错误信息!”);
        }
        List<DeclareTmpMsgData> msgDataList = new ArrayList<>();
        for (String msg : msgList) {
            DeclareTmpMsgData msgData = new DeclareTmpMsgData();
            msgData.setMsg(msg);
            msgDataList.add(msgData);
        }
        String fileName = "基地档案导入报错提示_" + DateTime.getCurrentDate_YYYYMMDD() + ".xlsx”;
        //创建流
        ByteArrayOutputStream bos = null;
        BufferedOutputStream bufferedOutputStream = null;
        String ossPath = null;
        try {
            bos = new ByteArrayOutputStream();
            bufferedOutputStream = new BufferedOutputStream(bos);
            EasyExcel.write(bufferedOutputStream, DeclareTmpMsgData.class).sheet("errorinfo").doWrite(msgDataList);
            ossPath = easyExcelFileCreator.getOssPath(bos, fileName);
        } finally {
            Tools.release(bufferedOutputStream);
        }
        return R.data(ossPath);
    }
利用内存解析工具

图片上传有问题...

内存分析工具,并未发现有大对象。考虑未在服务挂掉前dump文件,可能导致内存分析不是有效的。

优化代码后,重新打包发布,服务恢复正常。

总结:

  1. 对于文件流,或文件相关的初始化(代码层次,implements Closeable,实现接口Closeable),使用结束后,都要进行释放流操作,并且要严格遵守try-catch-finally。
  2. 对于代码不严谨,使得项目存在内存溢出的隐患,在测试环节不容易能测试出来,发布到线上,并发量大了,就会出现服务挂掉问题,怎样能够避免此类严重风险,成为考虑的重点。

我认为可以做到2点,第一,后端人员每个人的代码水平都不一样,需要定期对项目所用到的知识点讲解说明;第二,需要有经验的后端人员,对每一轮迭代的代码进行code review,排除代码风险隐患;

  1. 考虑Java进程出现问题的时候可能已经被系统kill掉。因此我们需要配置JVM,让他能够在特定时间点自动生成heap dump。
    conf文件中配置
    在OOM的时候生成heap dump:
    -XX:+HeapDumpOnOutOfMemoryError
    文件生成的路径需要配置
    -XX:HeapDumpPath=/yuanben/dumps/heapdump.hprof

操作linux用到的命令:
jps
jstat -gc pid 5000
free -m
jmap -dump:live,format=b,file=/yuanben/farm-web123.bin pid
或者
jcmd pid GC.heap_dump /yuanben/dumps/heapdump.hprof
tail -f /yuanben/xx.log
kill -9 pid

配置:服务器,2核4G,该web服务分配了512M内存-Xmx512m

工作中的每一次困难,都是一次成长

每个人工作都不一定是一帆风顺的,当在工作中遇到困难时,我们应该积极地去面对它,战胜它。因为每次战胜困难的过程都是自我学习的过程,它能给予我们知识力量,能够让我们积累经验,能够促使我们更加strong。

相关文章

网友评论

      本文标题:web服务挂了 java.lang.OutOfMemoryErr

      本文链接:https://www.haomeiwen.com/subject/pzwyydtx.html