
背景我自己部署SpringBoot项目时服务器是华为云最低配——1核2G。跑着一个MySQL、一个Redis、一个Nacos还剩多少内存给应用不到500M。如果按默认JVM参数跑一个SpringBoot启动就吃掉300M堆内存加上Metaspace和线程栈随随便便上500M。两个服务部署上去OOM。所以必须压JVM内存。最终我把单个服务堆内存压到了192M服务正常运行。分享这个过程中踩的坑和最终配置。选基础镜像SpringBoot官方推荐openjdk镜像但那玩意 400M。换成精简版FROM eclipse-temurin:17-jre-alpinejre而不是jdkalpine而不是完整版。体积从 400M 降到 180M。不需要JDK编译能力运行环境有JRE就够了。如果需要更极致的可以用distroless但调试不方便生产环境用 alpine 够好了。JVM内存怎么算总内存2G减去操作系统~200MMySQL~400MRedis~100MNacos~300MDocker本身~200M剩余约 800M。跑两个SpringBoot 一个网关每个应用不能超过 200M。JVM启动参数ENV JAVA_OPTS-Xms128m -Xmx192m \ -XX:MetaspaceSize64m -XX:MaxMetaspaceSize96m \ -XX:MaxDirectMemorySize32m \ -XX:ReservedCodeCacheSize32m \ -XX:UseSerialGC \ -XX:TieredCompilation \ -XX:TieredStopAtLevel1 \ -XX:UseContainerSupport \ -XX:InitialRAMPercentage25.0 \ -XX:MaxRAMPercentage50.0逐项解释为什么这么设堆内存 -Xms128m -Xmx192m初始128M最大192M。初始设低一点避免一启动就占满让其按需增长。192M对于大多数CRUD服务足够了——主要是查数据库和Redis不做什么计算密集操作。元空间 -XX:MetaspaceSize64m -XX:MaxMetaspaceSize96mSpringBoot一个项目上百个类元空间得给够。64M起步最大96M。实测下来不会超过80M。直接内存 -XX:MaxDirectMemorySize32mNIO用的。如果用了Netty比如WebClient这块要留一点。32M保守够用。GC策略 -XX:UseSerialGC单核CPU用SerialGC反而最快。别一上来就往G1上招呼小堆用SerialGC停顿短、内存开销小。编译优化 -XX:TieredCompilation -XX:TieredStopAtLevel1关闭C2编译只用C1。C2编译慢、内存消耗大。小应用不用追求极致吞吐C1编译足够还能省Metaspace。容器感知 -XX:UseContainerSupportDocker里必须加否则JVM默认读宿主机的内存而不是容器的。不加这个参数-XX:MaxRAMPercentage不会生效。百分比设限 -XX:InitialRAMPercentage25.0 -XX:MaxRAMPercentage50.0配合UseContainerSupport使用。如果Docker分配了400M内存最大堆只用到200M。比直接写-Xmx灵活。完整DockerfileFROM eclipse-temurin:17-jre-alpine RUN addgroup -S app adduser -S app -G app USER app WORKDIR /app COPY target/*.jar app.jar ENV JAVA_OPTS-Xms128m -Xmx192m \ -XX:MetaspaceSize64m -XX:MaxMetaspaceSize96m \ -XX:MaxDirectMemorySize32m \ -XX:ReservedCodeCacheSize32m \ -XX:UseSerialGC \ -XX:TieredCompilation -XX:TieredStopAtLevel1 \ -XX:UseContainerSupport \ -XX:InitialRAMPercentage25.0 -XX:MaxRAMPercentage50.0 EXPOSE 8080 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]几个注意点addgroup/adduser创建非root用户安全问题USER app之后的文件权限要注意EXPOSE 只是文档作用实际端口映射在 docker-compose 里配docker-compose 配置services: app-gateway: image: aipdt-java:v17 container_name: gateway ports: - 8080:8080 volumes: - ./gateway.jar:/app/app.jar - ./logs/gateway:/app/logs environment: - JAVA_OPTS-Xms128m -Xmx192m networks: - aipdt-cloud restart: unless-stopped app-workflow: image: aipdt-java:v17 container_name: workflow ports: - 8897:8897 volumes: - ./workflow.jar:/app/app.jar - ./logs/workflow:/app/logs environment: - JAVA_OPTS-Xms128m -Xmx192m networks: - aipdt-cloud restart: unless-stopped networks: aipdt-cloud: external: true配置外挂application.yml不同环境的差异通过Nacos配置中心管理镜像统一。踩过的坑1. alpine镜像DNS解析慢alpine默认用musl libcDNS解析行为跟glibc不同。有时服务间通过容器名通信会很慢。解决Java层面加-Djava.net.preferIPv4Stacktrue或者用openjdk:slim替代 alpine。2. Docker内存限制不等于JVM感知很多教程让你用docker run -m 400m限制容器内存然后JVM屁颠屁颠就适配了。不是的。必须加-XX:UseContainerSupportJava 10才有这个参数。如果没加JVM读到的是宿主机内存比如2G堆内存会设到500M然后超过容器的400M限制被Docker直接kill。3. 公网/内网端口不混用Docker容器间通信用内网container name外部访问才走宿主机端口。不要把数据库端口暴露到公网配置里只开放应用端口。总结低配服务器跑DockerJVM堆内存128-192M够用别用默认JVM参数手动配堆、元空间、GC-XX:UseContainerSupport是Docker必备配置外挂、镜像统一多环境一致性靠Nacos我正用一个人AI模式做独立开发微信小程序面狮狮已上线欢迎体验。更多技术分享https://gitee.com/yao113088/jiguang-dev