调整 JVM 最大堆内存参数 -Xmx 为 EC2 实例物理内存的 70%-80%,开启堆转储 -XX:+HeapDumpOnOutOfMemoryError,并检查系统日志确认是否触发 Linux OOM Killer。
先说结论:EC2 上 Java OOM 通常是堆内存设置超过实例可用物理内存或元空间不足,需根据实例规格重新计算 heap 大小。
- 先确认:区分 Java 堆溢出还是 Linux 系统级 OOM Killer 杀进程
- 先处理:调整 -Xmx 参数并预留操作系统内存,开启 HeapDump
- 再验证:观察 CloudWatch 内存监控及应用日志无重启记录
命令速用版
以下命令用于查看当前 Java 进程内存参数及系统内存状态,直接在 EC2 实例终端执行。
查看 Java 进程启动参数:
ps -ef | grep java
查看实例总内存:
free -h
查看系统 OOM 日志:
dmesg | grep -i 'out of memory'
推荐启动参数示例(假设实例 4GB 内存):
java -Xms2g -Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/java_heapdump.hprof -jar app.jar
为什么会这样
Java 应用内存溢出在 EC2 上主要有两种原因:JVM 堆内存设置过大触发 Linux 内核保护,或 JVM 内部堆/元空间确实不足。
EC2 实例有固定的物理内存上限。如果 JVM 参数 -Xmx 设置接近或超过实例总内存,加上 JVM 自身非堆内存(Metaspace、CodeCache、线程栈)和操作系统开销,总需求会超过物理限制。此时 Linux 内核 OOM Killer 会强制杀死 Java 进程,表现为应用突然消失且无 Java 异常栈。若未超过物理限制但堆已满,则抛出 java.lang.OutOfMemoryError: Java heap space。
分步处理
第一步:确认 OOM 类型
检查应用日志是否有 Java 异常栈。若日志突然中断且无异常,执行 dmesg | grep -i 'out of memory'。若看到 Kill process 记录,说明是系统级 OOM,需减小 -Xmx。若看到 Java heap space 异常,说明堆确实不够,可适当增大 -Xmx 但不可超过实例内存限制。
第二步:计算安全内存值
获取 EC2 实例总内存(如 free -h 显示 4GiB)。预留 20%-30% 给操作系统和非堆内存。例如 4GB 实例,-Xmx 建议设置为 2g 或 2.5g,不要设置为 4g。元空间 -XX:MaxMetaspaceSize 建议设置为 256m 或 512m,防止元空间无限增长占用堆外内存。
第三步:修改启动脚本
编辑 systemd 服务文件或启动脚本,更新 JAVA_OPTS 环境变量。确保添加 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath=/path/to/dump,以便下次溢出时保留现场。
第四步:重启应用
执行 systemctl restart 或重新运行启动脚本。重启后立即观察进程状态,确认未立即退出。
怎么验证是否生效
检查进程存活状态:使用 systemctl status 或 ps -ef 确认 Java 进程运行时间 uptime 持续增长,无频繁重启。
监控内存使用率:在 AWS CloudWatch 查看 EC2 实例 MemoryUtilization 指标。稳定运行时内存使用率不应长期维持在 95% 以上。若安装了 CloudWatch Agent,可监控 JVM 堆使用率。
检查日志文件:确认应用日志目录生成新的日志文件,且无新的 OOM 异常栈。检查 dmesg 确认无新的 OOM Killer 记录。
常见坑
坑一:Xmx 等于实例总内存。这是最常见错误。JVM 非堆内存和操作系统都需要内存,Xmx 等于总内存必触发 Linux OOM Killer。
坑二:忽略容器化限制。若在 EC2 上运行 Docker,需同时限制 Docker 容器内存和 JVM 内存,两者叠加不可超过实例内存。
坑三:未开启 HeapDump。溢出后无现场文件,无法分析是内存泄漏还是容量不足,导致后续无法根治。
坑四:32 位 JVM 限制。老旧 32 位 JVM 最大堆内存通常限制在 1.5GB 左右,即使实例内存很大也无法分配,需确认 java -version 输出为 64-Bit。
常见问题
Java OOM 和 Linux OOM Killer 有什么区别?
Java OOM 是 JVM 抛出的异常,应用可能捕获或崩溃但留有日志;Linux OOM Killer 是内核强制杀进程,应用日志突然中断,需在系统日志 dmesg 中查找。
Xmx 设置太小会有什么后果?
设置太小会导致频繁 Full GC,应用响应变慢甚至抛出 Java heap space 错误,但不会导致进程被系统杀死。
如何分析 HeapDump 文件?
使用 Eclipse MAT 或 JVisualVM 工具加载 hprof 文件,查看 Dominator Tree 定位占用内存最大的对象。
EC2 实例内存不够怎么办?
若已优化参数仍溢出,说明应用确实需要更多内存,需在 AWS 控制台停止实例并更改为更大内存规格实例类型,如从 t3.medium 升级到 t3.large。
参考来源
- Oracle Java Documentation, Java SE Documentation, https://docs.oracle.com/en/java/javase/
- AWS EC2 User Guide, Instance Types, https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html
- AWS Best Practices for Java, AWS Architecture Center, https://aws.amazon.com/architecture/