美文网首页
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