容器化 Java 应用最佳实践
Java 应用容器化有一个独特的挑战:JVM 默认不了解容器的资源限制。它可能「认为」自己有整个宿主机的资源,导致内存分配不合理、GC 过于频繁、CPU 使用不足。
这一篇讲解如何正确地将 Java 应用容器化,包括 Dockerfile 编写、资源配置、以及常见的坑。
为什么 Java 容器化特殊
JVM 默认行为的陷阱
传统部署中,JVM 读取宿主机的资源信息。容器化后,如果不加配置,JVM 读取的还是宿主机的资源:
这导致:
- 内存溢出:JVM 分配远超容器限制的内存
- CPU 不均衡:GC 线程数基于 CPU 核数设置
- 资源争抢:多个容器竞争宿主机资源
选择合适的基础镜像
镜像选择对比
推荐配置
Dockerfile
JVM 容器感知配置
Java 10+:自动容器检测
Java 10 引入了容器感知功能,JVM 会自动读取容器的 cgroup 限制:
Dockerfile
关键参数配置
推荐的 JVM 参数组合
生产环境推荐参数
Dockerfile 最佳实践
多阶段构建
Dockerfile
Gradle 构建
Dockerfile
Spring Boot 优化
Dockerfile
资源限制配置
Docker Compose 配置
docker-compose.yml
Kubernetes 配置
deployment.yaml
健康检查配置
Actuator 端点
application.yml
Docker 健康检查
Dockerfile
常见问题与排查
内存问题
GC 问题
性能监控
最佳实践清单
- 使用
-XX:+UseContainerSupport(Java 10+ 默认启用) - 设置合理的堆内存(MaxRAMPercentage=75)
- 选择合适的垃圾收集器(G1GC 或 ZGC)
- 使用多阶段构建减小镜像体积
- 配置健康检查和优雅关闭
- 日志输出到标准输出/错误
- 不要以 root 用户运行
- 配置正确的时区和 locale
延伸思考
Java 容器化的核心挑战是让 JVM「理解」容器。Java 10+ 已经解决了大部分问题,但生产环境中仍然需要谨慎配置。
建议:
- 监控 JVM 运行时指标:GC 频率、内存使用、线程数
- 在容器中压测:验证配置是否符合预期
- 考虑使用 GraalVM Native Image:彻底解决 JVM 问题
- Java 17+ 是最佳选择:容器支持更完善
容器化不只是把 JAR 包放到镜像里,而是重新思考应用的部署方式。JVM 的配置、资源的限制、健康的检查——每一个细节都会影响生产环境的稳定性。