核心思路是控制内存驻留规模而非盲目加内存:查询用Cursor替代List、流式处理避免隐式复制、运算前校验溢出、关键数值用BigDecimal、OOM时通过堆转储和GC日志定位泄漏源。

核心思路不是“加内存”,而是让数据不全进堆——尤其在查询、拷贝、计算等高频场景下,要主动控制内存驻留规模。
查数据库时别把整张表拖进内存
MyBatis 默认会把 SELECT * 的全部结果一次性加载为 List 对象,百万行 ≈ 几百 MB 堆空间。这不是 SQL 问题,是加载方式问题。
- 用
Cursor<User>替代List<User>:Mapper 接口方法返回Cursor,XML 中加fetchType="FOR UPDATE"或直接用selectCursor() - 确保 JDBC 驱动支持流式(如 MySQL 需设置
useCursorFetch=true&defaultFetchSize=100) - 必须手动
cursor.close(),否则连接和内存不会释放
处理大数组时避开隐式复制
像 IOUtils.copy(input, new ByteArrayOutputStream()) 这类调用,实际会在内存中同时存两份数据副本,峰值翻倍。
- 改用流式直传:
inputStream.transferTo(outputStream)(JDK 9+),或边读边写 - 大 byte[] 场景下,优先用
ByteBuffer.allocateDirect()分配堆外内存 - 避免
Arrays.asList(bigArray)或Stream.of(bigArray)—— 装箱后内存膨胀 3–5 倍
运算前做范围校验,别等溢出才报错
int 相加超限不会抛异常,而是静默回绕;long 也一样。业务逻辑里该拦住就得拦住。
立即学习“Java免费学习笔记(深入)”;
- 用 JDK 自带的溢出检查方法:
Math.addExact(a, b)、Math.multiplyExact(),触发ArithmeticException - 对用户输入或外部数据,先判断是否在合理区间(如订单金额不能 > 1 亿),再参与计算
- 关键数值字段建议用
BigDecimal,尤其涉及金额、精度要求高的场景
排查和兜底不能只靠 -Xmx
堆内存调大只是临时缓解,掩盖了真实瓶颈。OOM 发生后要能快速定位源头。
- 启动参数加上
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump,拿到 heap dump 后用 VisualVM 或 JProfiler 分析大对象来源 - 监控 GC 日志:频繁 Full GC + 释放很少,大概率是内存泄漏,重点查静态集合、ThreadLocal、缓存未清理
- 对长周期任务(如导出、同步),加内存使用阈值告警:运行时通过
Runtime.getRuntime().freeMemory()定期采样,接近阈值就主动中断或切分


















