本文目录导读:

避免 Java 内存溢出(OutOfMemoryError)需要从代码层面、JVM配置层面、数据管理层面以及监控分析层面进行系统性设计,以下是常见的场景及对应的优化策略:
代码与数据结构层面(最基础)
警惕大对象与集合的无限增长
- 典型场景:
List、Map等容器在循环中无限添加元素。 - 解决方案:
- 使用 固定大小的集合(如
ArrayDeque设置容量)或 流式处理(Stream搭配 limit)。 - 对于缓存,采用 WeakHashMap 或第三方缓存框架(Caffeine、Redis)并设置过期策略。
- 使用 固定大小的集合(如
优化字符串拼接
- 反面案例:频繁使用 拼接字符串(每次生成新对象)。
- 最佳实践:
- 单线程:
StringBuilder(非线程安全) - 多线程:
StringBuffer或StringJoiner
- 单线程:
避免无效的 Stream 与 Lambda 闭包
- 对流操作中的
collect(toList())谨慎使用,尤其是数据量极大时,考虑forEach或分批处理。 - 避免在循环外持有不需要的局部变量引用,防止闭包阻止垃圾回收。
及时关闭资源
- 使用
try-with-resources确保InputStream、Connection、ResultSet等及时关闭,防止文件描述符泄露和堆外内存增长。 - 对于
ThreadLocal,使用后务必调用.remove(),防止线程池复用导致的内存泄露。
JVM 参数调优(核心防线)
合理设置堆大小
-Xms4g -Xmx4g # 初始和最大堆相同,避免动态扩容开销 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m # 元空间(类加载)
选择合适的垃圾回收器(GC)
| 场景 | 推荐 GC | 参数示例 |
|---|---|---|
| 高吞吐、大堆(>4G) | G1 | -XX:+UseG1GC -XX:MaxGCPauseMillis=200 |
| 低延迟、大堆 | ZGC | -XX:+UseZGC |
| 小堆、单核 | Serial GC | -XX:+UseSerialGC |
监控与 dump 配置
-XX:+HeapDumpOnOutOfMemoryError # 自动 dump 堆快照 -XX:HeapDumpPath=/var/log/dump # 指定路径 -XX:+PrintGCDetails # 打印 GC 日志
数据与架构层面(重要)
分页查询代替全量加载
- 错误做法:
SELECT * FROM orders加载到内存再过滤。 - 正确做法:SQL 分页(
LIMIT offset, size)或游标(Cursor)逐条处理。
批量处理与流式读取
- 对于文件处理(如 CSV、日志),使用
BufferedReader逐行读取,而非一次性readAllLines()。 - 使用
Files.lines()或Stream的惰性处理。
对象池与连接池复用
- 数据库连接:使用 HikariCP、Druid 连接池,限制最大连接数。
- 线程池:Executors.newFixedThreadPool 避免无界队列。
- 直接内存(DirectBuffer):使用
Netty或PooledByteBufAllocator控制。
常见反模式与排查工具
内存溢出的几种典型症状
- Java heap space:堆内存不足 → 检查集合、大对象、缓存。
- Metaspace:类加载过多 → 检查动态代理、JSP、Lambda 数量。
- Direct buffer memory:NIO 使用不当 → 检查 ByteBuffer 释放。
- unable to create new native thread:线程数过多 → 检查线程池配置。
快速定位工具
| 工具 | 用途 |
|---|---|
jps |
查看 Java 进程 PID |
jmap -heap <pid> |
查看堆使用概况 |
jmap -dump:live,format=b,file=dump.hprof <pid> |
生成堆 dump |
jstat -gcutil <pid> |
实时 GC 监控 |
| MAT (Eclipse Memory Analyzer) | 分析堆 dump,查找大对象和泄漏点 |
| VisualVM | 可视化监控线程、GC、CPU 和内存 |
实战案例:修复一个经典的 OOM
代码问题:
public List<Order> queryAllOrders() {
return orderDao.selectAll(); // 200万条记录,OOM
}
修复方案(二选一):
- 分页流式:
public void processOrders(Consumer<Order> processor) { try (Stream<Order> stream = orderDao.streamAll()) { stream.forEach(processor); } } - 游标 + 限流:使用 Spring Data JPA 的
Pageable。
避坑清单
- ✅ 集合、缓存一定要设置 容量上限 或 过期策略。
- ✅ 一次性加载大数据时,改为 流式处理 或 分页。
- ✅ 开启
HeapDumpOnOutOfMemoryError并设置 dump 路径。 - ✅ 生产环境使用 G1 或 ZGC,并配合 GC 日志观察停顿。
- ✅ 定期用 VisualVM 或 MAT 分析堆,排查占用高的对象。
没有银弹,内存优化是一个“监控 → 分析 → 调优 → 验证”的持续过程。