一、什么是内存溢出?

想象你的手机只有64G存储,当照片和视频塞满后,系统就会提示"存储空间不足"。JVM内存溢出也是类似情况:Java程序运行时需要的内存超过了JVM分配的上限,就像往500ml的杯子里倒600ml水,多出来的部分就会溢出。

常见报错长这样:

// 技术栈:Java 8
public class OverflowDemo {
    public static void main(String[] args) {
        List<byte[]> leakList = new ArrayList<>();
        while (true) {
            // 持续申请1MB内存却不释放
            leakList.add(new byte[1024 * 1024]); 
        }
    }
}
/* 运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
*/

二、为什么会溢出?

2.1 内存泄漏(举着水杯不放手)

就像借书不还导致图书馆没书可借,某些对象已经不用了,却被意外保留引用无法回收。比如:

// 技术栈:Java 8
public class LeakExample {
    static Map<Long, Object> cache = new HashMap<>();

    public void addToCache(long id) {
        // 缓存对象但忘记设置淘汰机制
        cache.put(id, new byte[1024 * 1024]);
    }
}
/* 随着时间推移,cache会吃掉所有堆内存 */

2.2 配置不足(杯子太小)

新生代和老年代比例不合理,比如电商大促时:

// 启动参数配置不当示例
-Xms128m -Xmx128m  // 只给128MB堆内存
// 实际需要处理10万订单数据时:
List<Order> orders = queryAllOrders(); // 瞬间爆炸

三、实战排查四步走

3.1 收集证据(拍下案发现场)

用JDK自带工具快速取证:

# 查看内存概况
jmap -heap <pid>

# 生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>

3.2 分析线索(侦探时间)

使用MAT工具分析hprof文件,重点关注:

  1. 占用空间最大的对象(按Size排序)
  2. 对象引用链(Path to GC Roots)

3.3 修复方案

案例1:线程池未关闭

// 错误示范:线程池用完没shutdown
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(() -> { /* 耗时任务 */ });

// 正确做法:添加钩子关闭
Runtime.getRuntime().addShutdownHook(
    new Thread(pool::shutdownNow)
);

案例2:缓存失控

// 使用WeakHashMap自动清理
Map<Key, BigObject> cache = 
    Collections.synchronizedMap(new WeakHashMap<>());

// 或者用Guava Cache设置上限
Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build();

四、防患于未然

4.1 合理配置参数

根据应用特点调整:

# 电商类应用建议
-Xms4g -Xmx4g -XX:NewRatio=2 
-XX:+UseG1GC -XX:MaxGCPauseMillis=200

4.2 监控预警

Spring Boot Actuator配置示例:

management:
  endpoints:
    web:
      exposure:
        include: health,metrics
  metrics:
    tags:
      application: ${spring.application.name}

4.3 代码规范

避免常见陷阱:

  1. 慎用静态集合
  2. 及时关闭IO流
  3. 大数据集分批次处理

五、特别场景处理

5.1 堆外内存溢出

报错特征:OutOfMemoryError: Direct buffer memory
解决方案:

// 调整MaxDirectMemorySize
-XX:MaxDirectMemorySize=256m

// 显式回收ByteBuffer
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
((DirectBuffer) buffer).cleaner().clean();

5.2 元空间溢出

报错特征:OutOfMemoryError: Metaspace
动态加载类场景需注意:

# 调整元空间大小
-XX:MetaspaceSize=128m 
-XX:MaxMetaspaceSize=256m

六、总结与避坑指南

应用场景:高并发系统、长时间运行服务、大数据处理
技术优劣

  • 优点:工具链成熟,大部分问题可定位
  • 缺点:某些内存泄漏需要长期监控才能发现

注意事项

  1. 生产环境慎用-XX:+HeapDumpOnOutOfMemoryError
  2. 堆转储文件可能很大(建议单独磁盘存储)
  3. 容器化部署时注意cgroup内存限制

记住三原则:

  1. 给内存设合理上限
  2. 像管理钱包一样管理对象引用
  3. 重要系统添加内存水位监控