Skip to content

解决内存溢出

1. 修复问题思路

修复内存溢出问题的要具体问题具体分析,问题总共可以分成三类:

1.1 代码中的内存泄漏

代码中的内存泄漏在前面的篇章中已经介绍并提供了解决方案

1.2 并发引起内存溢出-参数不当

由于参数设置不当,比如堆内存设置 过小,导致并发量增加之后超过堆内存的上限。
解决方案:调整参数

1.3 并发引起内存溢出–设计不当

系统的方案设计不当,比如:

  • 从数据库获取超大数据量的数据
  • 线程池设计不当
  • 生产者-消费者模型,消费者消费性能问题

解决方案:优化设计方案

2. 分页查询文章接口的内存溢出

小李负责的新闻资讯类项目采用了微服务架构,其中有一个文章微服务,这个微服务在业务高峰期出现了内存溢出的现象。

2.1 问题根源

文章微服务中的分页接口没有限制最大单次访问条数,并且单个文章对象占用的内存量较大,在业务高峰期并发量较大时这部分从数据库获取到内存之后会占用大量的内存空间。 354346546342342.png

2.2 测试代码

java
// Controller 层
@GetMapping
public ResponseEntity<Page<TbArticle>> queryByPage(TbArticle tbArticle, int page, int size) {
    return ResponseEntity.ok(this.articleservice.queryByPage(tbArticle, PageRequest.of(page, size)));
}
// Service 层
/**
 * 分页查询
 * @param tbArticle
 * 筛选条件
 * @param pageRequest 分页对象
 * @return查询结果
 **/
@Override
 public Page<TbArticle> queryByPage(TbArticle tbArticle, PageRequest pageRequest) {
     //long total = this.articleDao.count(tbArticle); // 总条数先不管减少干扰, 直接为0
     return new PageImpl<>(this.articleDao.queryAllBylimit(tbArticle, pageRequest), pageRequest, 0);
 }

启动文章微服务:

sh
java -jar -Dserver.port=8081 -Darthas.http-port=3661 -Darthas.telnet-port=8565 -Xmx512m -Xms512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jvm/dump/jvm.hprof jvm-optimize-0.0.1-SNAPSHOT.jar

访问文章微服务: ScreenShot_2026-05-12_073727_981.png 数据库数据,可以看到文章内容字段是很长内容: ScreenShot_2026-05-12_073843_491.png 如果持续不断有人访问这个接口,就会导致内存溢出。打开这个jvm.hprof,假设我们不知情,如何找到这个接口导致的内存溢出?

2.3 问题分析

使用MAT工具打开jvm.hprof文件,进行分析:
ScreenShot_2026-05-12_074624_655.png MAT提示我们有两个怀疑对象,第一个占用200.4MB,第二个占用153.2MB。MAT怀疑的对象1信息: ScreenShot_2026-05-12_080417_581.png 可以看出,第一个对象堆栈中使用多线程,端口是8081. ScreenShot_2026-05-14_112942_324.png 第二个对象是MySQL驱动的返回对象。大致能推测可能是大量的线程请求数据库,导致内存溢出。查看直方图和支配树,进一步分析:
ScreenShot_2026-05-14_114346_895.png 由于是SpringBoot项目,通过查看类HandlerMethod方法, ScreenShot_2026-05-14_172939_478.png 选中右键ListObjects, 选择Outgoing,查看里面包含的具体的Controller: ScreenShot_2026-05-24_204523_589.png 我们就能快速的找到当前线程执行的是那个接口方法:
ScreenShot_2026-05-24_205930_484.png 回看这个接口的代码,如果内存溢出,势必在内存中会有大量TbArticle对象, 在MAT的直方图中搜索这个Entity对象: ScreenShot_2026-05-24_211248_111.png 很明显TbArticle对象的数量远远超过其他的对象,TbArticle对象就是内存溢出对象.当然更加严谨的做法还需要使用JMeter去压力测试这个接口(最好在开发或者测试环境是去复现),查看问题是否复现.

2.4 解决思路

  1. 与产品设计人员沟通,限制最大的单次访问条数。
  2. 分页接口如果只是为了展示文章列表,不需要获取文章内容,可以大大减少对象的大小。
  3. 在高峰期对微服务进行限流保护。

3. Mybatis导致的内存溢出

小李负责的文章微服务进行了升级,新增加了一个判断id是否存在的接口,第二天业务高峰期再次出现了内存溢出,小李觉得应该和新增加的接口有关系。 ScreenShot_2026-05-24_223409_525.png

3.1 生成内存快照

由于提前配置启动脚本宕机时候自动生成内存快照, 所以服务器上面对应的目录下已经生成了内存快照leak.hprof。

3.2 问题分析

使用MAT工具打开leak.hprof文件,进行分析: ScreenShot_2026-05-24_223942_971.png 直接查看直方图,查看所有的对象列表: ScreenShot_2026-05-24_224322_006.png 可以按照深堆降序展示,快速看到那些对象最占内存: ScreenShot_2026-05-24_224759_040.png 可以看到最多的是Tomcat的线程对象, 也可以通过查看Biggest Instances来看: ScreenShot_2026-05-24_225142_970.png 通过支配树界面,去分析线程背后的代码接口: ScreenShot_2026-05-24_231002_565.png 找一个大对象,比如第一个点击展开:
ScreenShot_2026-05-24_231135_241.png 由于是SpringBoot项目,通过查看类HandlerMethod方法, ScreenShot_2026-05-24_231233_809.png 查看有它引用了那些对象,其中有一个description的对象,里面记录了接口信息: ScreenShot_2026-05-24_231938_032.png 然后根据接口定位到service层代码:

java
/**
* 判断批量id存在多少个
* @GetMapping 
**/
public ResponseEntity countIfAbsent(int size) {
    //随机生成批量id
    List<Integer> ids = new Random().ints(0, 10000).limit(size).boxed().collect(Collectors.tolist());
    return ResponseEntity.ok(this.articleService.countIfAbsent(ids));
}

相关的Mapper层代码:

xml
<select id="countIfAbsent" resultType="java.lang.Long">
    select
    IFNULL(sum(1),0)
    from article where
    <if test="ids!=null and ids.size()>0">
        id in
        <foreach collection="ids" item="item"  open="(" close=")" separator=",">
            #{item}
        </foreach>
    </if>
</select>

Mybatis在使用foreach进行sql拼接时,会在内存中创建对象,如果foreach处理的数组或者集合元素个数过多,会占用大量的内存空间: ScreenShot_2026-05-24_234706_983.png 同时在xml中的foreach标签也会受到影响,会产生大量HashMap对象:
ScreenShot_2026-05-24_235315_962.png HashMap对象中的key保存的就是in查询的id值: ScreenShot_2026-05-24_235412_562.png

3.3 解决思路

  1. 限制参数中最大的id个数。
  2. 将id缓存到Redis或者内存缓存中,通过缓存进行校验。

4. 导出大文件内存溢出

小李负责了一个管理系统,这个管理系统支持几十万条数据的excel文件导出。他发现系统在运行时如果有几十个人同时进行大数据量的导出,会出现内存溢出。 ScreenShot_2026-05-25_000326_738.png 小李团队使用的是k8s将管理系统部署到了容器中,k8s整体规划如下: ScreenShot_2026-05-25_000642_487.png

5. ThreadLocal使用时占用大量内存

6. 文章内容审核接口的内存问题