【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)
前言
OOM 几乎是笔者工作中遇到的线上 bug 中最常见的,一旦平时正常的页面在线上出现页面崩溃或者服务无法调用,查看服务器日志后你很可能会看到“Caused by: java.lang.OutOfMlemoryError: Java heap space” 这样的提示,那么毫无疑问表示的是 Java 堆内存溢出了。
其中又当属集合内存溢出最为常见。你是否有过把整个数据库表查出来的全字段结果直接赋值给一个 List 对象?是否把未经过过滤处理的数据赋值给 Set 对象进行去重操作?又或者是在高并发的场景下创建大量的集合对象未释放导致 JVM 无法自动回收?
Java 堆内存溢出
我的解决方案的核心思路有两个:一是从代码入手进行优化;二是从硬件层面对机器做合理配置。
一、代码优化
下面先说从代码入手怎么解决。
1.1Stream 流自分页
/**
* 以下示例方法都在这个实现类里,包括类的继承和实现
*/
@Service
public class StudyServiceImpl extends ServiceImpl<StudyMapper, Study> implements StudyService{}
在循环里使用 Stream 流的 skip()+limit() 来实现自分页,直至取出所有数据,不满足条件时终止循环
/**
* 避免集合内存溢出方法(一)
* @return
*/
private List<StudyVO> getList(){
ArrayList<StudyVO> resultList = new ArrayList<>();
//1、数据库取出源数据,注意只拿 id 字段,不至于溢出
List<String> idsList = this.list(new LambdaQueryWrapper<Study>()
.select(Study::getId)).stream()
.map(Study::getId)
.collect(Collectors.toList());
//2、初始化循环
boolean loop = true;
long number = 0;
long perSize = 5000;
while (loop){
//3、skip()+limit()组合,限制每次只取固定数量的 id
List<String> ids = idsList.stream()
.skip(number * perSize)
.limit(perSize)
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(ids)){
//根据第3步的 id 去拿数据库的全字段数据,这样也不至于溢出,因为一次只是 5000 条
List<StudyVO> voList = this.listByIds(ids).stream()
.map(e -> e.copyProperties(StudyVO.class))
.collect(Collectors.toList());
//addAll() 方法也比较关键,快速地批量添加元素,容量是比较大的
resultList.addAll(voList);
}
//4、判断是否跳出循环
number++;
loop = ids.size() == perSize;
}
return resultList;
}
1.2数据库分页
这里是用数据库语句查询符合条件的指定条数,循环查出所有数据,不满足条件就跳出循环
/**
* 避免集合内存溢出方法(二)
* @param param
* @return
*/
private List<StudyVO> getList(String param){
ArrayList<StudyVO> resultList = new ArrayList<>();
//1、构造查询条件
String id = "";
//2、初始化循环
boolean loop = true;
int perSize = 5000;
while (loop){
//分页,固定每次循环都查 5000 条
Page<Study> studyPage = this.page(new Page<>
(NumberUtils.INTEGER_ZERO, perSize),
wrapperBuilder(param, id));
if (Objects.nonNull(studyPage)){
List<Study> studyList = studyPage.getRecords();
if (CollectionUtils.isNotEmpty(studyList)){
//3、每次截取固定数量的标识,数组下标减一
id = studyList.get(perSize - NumberUtils.INTEGER_ONE).getId();
//4、判断是否跳出循环
loop = studyList.size() == perSize;
//添加进返回的 VO 集合中
resultList.addAll(studyList.stream()
.map(e -> e.copyProperties(StudyVO.class))
.collect(Collectors.toList()));
}
else {
loop = false;
}
}
}
return resultList;
}
/**
* 条件构造
* @param param
* @param id
* @return
*/
private LambdaQueryWrapper<Study> wrapperBuilder(String param, String id){
LambdaQueryWrapper<Study> wrapper = new LambdaQueryWrapper<>();
//只查部分字段,按照 id 的降序排列,形成顺序
wrapper.select(Study::getUserAvatar)
.eq(Study::getOpenId, param)
.orderByAsc(Study::getId);
if (StringUtils.isNotBlank(id)){
//这步很关键,只查比该 id 值大的数据
wrapper.gt(Study::getId, id);
}
return wrapper;
}
1.3其它思考
以上从根本上还是解决不了内存里处理大量数据的问题,取出 50w 数据放内存的风险就很大了。以下是我的其它解决思路:
- 从业务上拆解:明确什么情况下需要后端处理这么多数据?是否可以考虑在业务流程上进行拆解?或者用其它形式的页面交互代替?
- 数据库设计:数据一般都来源于数据库,库/表设计的时候尽量将表与表之间解耦,表字段的颗粒度放细,即多表少字段,查询时只拿需要的字段;
- 数据放在磁盘:比如放到 MQ 里存储,然后取出的时候注意按固定数量批次取,并且注意释放资源;
- 异步批处理:如果业务对实时性要求不高的话,可以异步批量把数据添加到文件流里,再存入到 OSS 中,按需取用;
- 定时任务处理:询问产品经理该功能或者实现是否是结果必须的?是否一定要同步处理?可以考虑在一个时间段内进行多次操作,缓解大数据量的问题;
- 咨询大数据团队:寻求大数据部门团队的专业支持,对于处理海量数据他们是专业的,看能不能提供一些可参考的建议。
二、硬件配置
核心思路:加大服务器内存,合理分配服务器的堆内存,并设置好弹性伸缩规则,当触发告警时自动伸缩扩容,保证系统的可用性。
2.1云服务器配置
以下是阿里云 ECS 管理控制台的编辑页面,可以对 CPU 和内存进行配置。在 ECS 实例伸缩组创建完成后,即可以根据业务规模去创建一个自定义伸缩配置,在业务量大的时候会触发自动伸缩。
阿里云 ECS 管理
如果是部署在私有云服务器,需要对具体的 JVM 参数进行调优的话,可能还得请团队的资深大佬、或者运维团队的老师来帮忙处理。
三、文章小结
本篇文章主要是记录一次线上 bug 的处理思路,在之后的文章中我会分享一些关于真实项目中处理高并发、缓存的使用、异步/解耦等内容,敬请期待。
那么今天的分享到这里就结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!
【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)的更多相关文章
- 《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读
堆内存使用分析,GC 日志解读 重要的东东 在Java中,对象实例都是在堆上创建.一些类信息,常量,静态变量等存储在方法区.堆和方法区都是线程共享的. GC机制是由JVM提供,用来清理需要清除的对象, ...
- 关于 Unity 项目中的 Mono 堆内存泄露
关于 Unity 项目中的 Mono 堆内存泄露 题记:这是补一篇应该在将近一年前就应该写的记录,今天终于补上. 内存泄露是一个老话题了,之前我专门写过一篇 排查 Lua 虚拟机内存泄露 的文章,并且 ...
- Java抛出OutOfMemoryError:Java heap space堆内存溢出错误的分析方案
抛出堆内存溢出的错误一定要记得保留现场环境(导出堆内存信息到文件),否则如果无法进行分析,并从根本上解决问题,下次很有可能还会出现. 第一步:导出堆转储文件 我们可以使用Jdk自带的jmap工具.使用 ...
- [JVM教程与调优] 了解JVM 堆内存溢出以及非堆内存溢出
在上一章中我们介绍了JVM运行时参数以及jstat指令相关内容:[JVM教程与调优] 什么是JVM运行时参数?.下面我们来介绍一下jmap+MAT内存溢出. 首先我们来介绍一下下JVM的内存结构. J ...
- OutOfMemoryError/OOM/内存溢出异常实例分析--堆内存溢出
Java堆内存溢出 只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象, 那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常,代码如下: import ...
- 自己挖的坑自己填--jxl进行Excel下载堆内存溢出问题
今天在进行使用 jxl 进行 Excel 下载时,由于数据量大(4万多条接近5万条数据的下载),数据结构过于负责,存在大量大对象(虽然在对象每次用完都设置为null,但还是存在内存溢出问题),加上本地 ...
- StringBuilder 导致堆内存溢出
StringBuilder 导致堆内存溢出 原始问题描述: Exception in thread "main" java.lang.OutOfMemoryError: Java ...
- 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便
Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...
- 关于java堆内存溢出的几种情况(转)
[情况一]: java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环: 如果是java堆内存不够 ...
- eclipse:Tomcat设置jvm,解决java.lang.OutOfMemoryError: Java heap space 堆内存溢出
eclipse 有启动参数里设置jvm大小,因为eclipse运行时自己也需要jvm,所以eclipse.ini里设置的jvm大小不是具体某个程序运行时所用jvm的大小,这和具体程序运行的jvm大小无 ...
随机推荐
- 【js】 Object.prototype.toString.call()
1,Object.prototype.toString这个方法的作用是什么 判断数据类型 2,为什么要用这个方法 是因为 js 中 一般的类型判断 对于 null,数组,对象 , 都会返回一样的结 ...
- sql server 数据恢复
1) 备份当前数据库的事务日志:BACKUP LOG [数据库名] TO disk= N'备份文件名' WITH NORECOVERY 2) 恢复一个误删除之前的完全备份:RESTORE DATABA ...
- [转帖]深入JVM - Code Cache内存池
深入JVM - Code Cache内存池 1. 本文内容 本文简要介绍JVM的 Code Cache(本地代码缓存池). 2. Code Cache 简要介绍 简单来说,JVM会将字节码编译为本地机 ...
- [转帖]【SQL SERVER】锁机制
https://www.cnblogs.com/WilsonPan/p/12618849.html 锁定是 SQL Server 数据库引擎用来同步多个用户同时对同一个数据块的访问的一种机制. 基 ...
- [转帖]云平台部署CNA、VRM手动安装方法
云平台部署CNA.VRM手动安装方法 分享人:郭道川 00443725 日期:2018.11.06 Ⅰ. 项目介绍 该项目主要为XX煤矿智能煤炭项目云平台部署交付,该项目所采用的服务器为RH2 ...
- Inspur CS5280H BMC重装系统的过程
Inspur CS5280H BMC重装系统的过程 背景 公司里面一台信创海光的设备 默认安装了银河麒麟v10的操作系统 但是在进行瀚高数据库压测时 总会出现无缘无故的宕机的情况. 昨天还特别学习了下 ...
- 【代码片段分享】比 url.QueryEscape 快 7.33 倍的 FastQueryEscape
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 做 profile 发现 url.QueryEscape ...
- Go 泛型发展史与基本介绍
Go 泛型发展史与基本介绍 Go 1.18版本增加了对泛型的支持,泛型也是自 Go 语言开源以来所做的最大改变. 目录 Go 泛型发展史与基本介绍 一.为什么要加入泛型? 二.什么是泛型 三.泛型的来 ...
- 从 WebStorm 转到 VSCode!使用一周体验报告
前言 最近我的 Jetbrains 开源项目授权到期了,想要续订的时候发现 Jetbrains 提高了开源项目申请门槛,我的 StarBlog 项目因为名字里包含 blog 这个词无法申请,虽然我在 ...
- 19.12 Boost Asio 获取远程进程
远程进程遍历功能实现原理与远程目录传输完全一致,唯一的区别在于远程进程枚举中使用EnumProcess函数枚举当前系统下所有活动进程,枚举结束后函数返回一个PROCESSENTRY32类型的容器,其中 ...