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

目录

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

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

坏代码的后果:

好代码的效果:

三、三个优化点详细讲解

[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. 大数据量接口超时

六、最简单记忆口诀

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

相关推荐
用户2986985301410 小时前
告别手动复制:Java 拆分 Word 文档的两种实用方案
java·后端
ujainu小10 小时前
CANN hixl:大模型 PD 分离场景的零拷贝通信库
android·java·缓存
z2005093010 小时前
今日算法(组合问题III)(回溯的使用)
java·算法·leetcode
XiYang-DING10 小时前
【Java EE】IPv6
java·java-ee·php
Re_zero10 小时前
从乐观锁被冲烂到原子扣减稳如磐石:高并发防超卖方案的三次迭代
java·后端
落木萧萧82510 小时前
自动生成 SQL 会拖慢性能吗?实测 MyBatisGX、MyBatis、MyBatis-Plus、MyBatis-Flex
java·orm
自律懒人10 小时前
阿里Qoder 1.0实测:对比Cursor和Claude Code,国产AI编程工具做到哪一步了?
jvm·深度学习·ai编程
Full Stack Developme10 小时前
Spring Boot 状态机 与 com.alibaba.cola 中的状态机
java·spring boot·后端
MacroZheng11 小时前
让 Claude Code 成本爆降 89%,这个开源工具有点猛...
java·人工智能·后端
likerhood11 小时前
Java 异常处理:从 try-catch-finally 到项目最佳实践
java·开发语言·php