拆分大对象 + 流式处理 + 不一次性加载全量数据

目录

一、先讲核心思想(一句话记住)

二、为什么要这么优化?(生产血泪教训)

坏代码的后果:

好代码的效果:

三、三个优化点详细讲解

[1. 拆分大对象(Split Large Objects)](#1. 拆分大对象(Split Large Objects))

什么是大对象?

为什么危险?

怎么拆分?

[示例:坏代码 vs 好代码](#示例:坏代码 vs 好代码)

[❌ 坏代码(一次性加载)](#❌ 坏代码(一次性加载))

[✅ 好代码(分批加载 + 拆分)](#✅ 好代码(分批加载 + 拆分))

[2. 流式处理(Stream / 流式读取)](#2. 流式处理(Stream / 流式读取))

什么是流式处理?

优势:

[Java 中两种流式写法:](#Java 中两种流式写法:)

[① 数据库流式查询(MyBatis / JDBC)](#① 数据库流式查询(MyBatis / JDBC))

[✅ 正确写法(MyBatis 流式查询)](#✅ 正确写法(MyBatis 流式查询))

[② Java8 Stream 流式处理(不存大 List)](#② Java8 Stream 流式处理(不存大 List))

[❌ 坏代码](#❌ 坏代码)

[✅ 流式处理(不创建临时大集合)](#✅ 流式处理(不创建临时大集合))

[3. 不一次性加载全量数据(核心!)](#3. 不一次性加载全量数据(核心!))

错误做法:

正确做法:

[示例:Excel 导入优化(超级经典)](#示例:Excel 导入优化(超级经典))

[❌ 坏代码(一次性读取全部行)](#❌ 坏代码(一次性读取全部行))

[✅ 好代码(流式逐行读取)](#✅ 好代码(流式逐行读取))

四、三合一终极实战代码(生产标准模板)

[场景:导出 10 万用户数据(不 OOM、不卡顿)](#场景:导出 10 万用户数据(不 OOM、不卡顿))

这个代码的优势:

五、这三个优化解决了什么生产问题?

六、最简单记忆口诀


这是生产环境解决 OOM、频繁 GC、大对象卡顿终极三板斧 。本文会用最通俗的语言 + 真实业务场景 + 可直接复制的代码,把这三个优化点讲透。


一、先讲核心思想(一句话记住)

不要把所有数据一次性全部加载到内存里,不要创建超大对象 / 超大集合,要像 "水管流水" 一样,读一点、处理一点、释放一点。

这就是:流式处理 + 分页 / 游标读取 + 小对象处理


二、为什么要这么优化?(生产血泪教训)

坏代码的后果:

  1. 一次性加载 10w 条数据到 List → 占几百 MB 内存
  2. 超大对象(10MB+) → 直接进入老年代
  3. FullGC 频繁 → 接口超时、服务卡死
  4. 最终 OOM 崩溃

好代码的效果:

  • 内存占用始终稳定
  • 无大对象
  • YGC 少,FGC 几乎为 0
  • 处理百万数据也不崩

三、三个优化点详细讲解

1. 拆分大对象(Split Large Objects)

什么是大对象?

  • 单个对象 > 10MB
  • 超大 List/Map 装几万条数据
  • 一次性加载整个文件、Excel、大报文

为什么危险?

JVM 规则:大对象直接进入老年代,老年代满了就触发 FullGC。

怎么拆分?

  1. 大集合拆成小批量(100~1000 条一批)
  2. 大报文拆字段,不需要的不加载
  3. 大文件分段读,不一次性读入内存

示例:坏代码 vs 好代码

❌ 坏代码(一次性加载)
复制代码
// 一次性加载10万条,占巨大内存
List<User> userList = userMapper.selectAll(); 
for(User user : userList){
    // 处理
}
✅ 好代码(分批加载 + 拆分)
复制代码
// 每次只查1000条
int pageSize = 1000;
int pageNum = 1;
while(true){
    List<User> userList = userMapper.selectPage(pageNum, pageSize);
    if(userList.isEmpty()) break;
    
    // 处理小批量数据
    processList(userList);
    
    pageNum++;
}

2. 流式处理(Stream / 流式读取)

什么是流式处理?

数据像水流一样,来一条处理一条,不全部存内存。

优势:

  • 内存占用极低
  • 无大集合
  • 处理完立即释放

Java 中两种流式写法:

① 数据库流式查询(MyBatis / JDBC)

✅ 正确写法(MyBatis 流式查询)
复制代码
// Mapper 接口(注解方式)
@Select("SELECT * FROM user")
@ResultType(User.class)
Cursor<User> selectAllUserStream(); // Cursor = 流

// 业务代码:逐条读取,不加载全量
try (Cursor<User> cursor = userMapper.selectAllUserStream()) {
    for (User user : cursor) {
        // 逐条处理,内存只存1条
        handleUser(user);
    }
}

内存几乎不涨!


② Java8 Stream 流式处理(不存大 List)

❌ 坏代码
复制代码
List<String> nameList = new ArrayList<>();
for(User user : userList){
    nameList.add(user.getName());
}
✅ 流式处理(不创建临时大集合)
复制代码
userList.stream()
       .map(User::getName)
       .forEach(this::sendMessage); // 直接处理,不存储

3. 不一次性加载全量数据(核心!)

错误做法:

复制代码
List data = queryAll(); // 全量加载 → OOM源头

正确做法:

  1. 分页查询
  2. 流式查询
  3. 游标 / 迭代器读取
  4. 文件分段读取

示例:Excel 导入优化(超级经典)

❌ 坏代码(一次性读取全部行)
复制代码
List<Row> rows = sheet.getRows(); // 加载10万行到内存
for(Row row : rows){
    // 处理
}
✅ 好代码(流式逐行读取)
复制代码
// 一行行读,读完即释放,内存只存一行
ExcelReader reader = ExcelUtil.getReader(file);
reader.read(this::processRow); // 流式消费

// 处理方法
void processRow(UserRow row){
    // 处理单行
}

四、三合一终极实战代码(生产标准模板)

场景:导出 10 万用户数据(不 OOM、不卡顿)

复制代码
@Transactional(readOnly = true) // 流式查询必须开启事务
public void exportUser(OutputStream outputStream){
    // 1. 数据库流式查询(不加载全量)
    try (Cursor<User> cursor = userMapper.streamAllUser()) {
        
        // 2. 流式写入Excel(不创建大对象)
        ExcelWriter writer = ExcelUtil.getWriter(outputStream);
        writer.write(cursor.stream()  // 3. Java流式处理
                     .map(this::convertToVO) 
                     .peek(item -> {
                         // 逐条处理
                     }));
        
        writer.flush();
    }
}

这个代码的优势:

  • 内存永远只占用几十 KB
  • 不会产生大对象
  • 不会频繁 GC
  • 处理 100 万数据也不会 OOM

五、这三个优化解决了什么生产问题?

  1. OOM
  2. 频繁 FullGC
  3. 大对象导致卡顿
  4. 内存持续上涨
  5. 大数据量接口超时

六、最简单记忆口诀

不一次性加载, 不创建大对象, 用流式一条条处理。

相关推荐
小bo波13 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
nanxun8862 天前
记一次诡异的 Docker 容器"串包"故障排查
java
用户1563068103512 天前
Day01 | Java 基础(Java SE)
java
行者全栈架构师2 天前
Maven dependency:tree 的 8 个高级用法
java·后端
行者全栈架构师2 天前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_02 天前
mac(m5)平台编译openjdk
java
唐青枫3 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马3 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户3721574261353 天前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户3721574261353 天前
Java 打印 Word 文档:从基础打印到高级设置
java