在 Linux 服务器上部署 Java 应用,看似简单(java -jar app.jar),但要达到生产环境的稳定性、高性能和可维护性,需要在多个维度进行精细配置。这不仅仅是代码层面的事,更多是操作系统、JVM 和中间件之间的协同。
以下是我从实际运维和架构角度总结的关键配置点,分为 系统层、JVM 层、应用层 和 运维监控层。
一、 系统层基础配置(地基)
很多 Java 性能问题或启动失败,根源不在 JVM,而在 Linux 内核参数。
-
文件描述符限制(File Descriptors)
- 问题:Java 应用(尤其是高并发 Web 服务、NIO 模型)会打开大量 socket 连接。Linux 默认单进程文件描述符限制通常为 1024,极易导致
Too many open files错误。 - 配置:修改
/etc/security/limits.conf或 systemd 的 service 文件。# /etc/security/limits.conf * soft nofile 65535 * hard nofile 65535 - 验证:使用
ulimit -n检查当前会话是否生效。如果是通过 systemd 管理的服务,需在.service文件中添加LimitNOFILE=65535。
- 问题:Java 应用(尤其是高并发 Web 服务、NIO 模型)会打开大量 socket 连接。Linux 默认单进程文件描述符限制通常为 1024,极易导致
-
内存交换策略(Swappiness)
- 问题:Java 堆外内存(Off-Heap)和直接内存(Direct Memory)不受 GC 完全控制。如果系统 Swap 活跃度过高,会导致严重的 Page Fault,引发 Full GC 停顿甚至 OOM。
- 配置:建议将
vm.swappiness设置为 0 或 1。sysctl vm.swappiness=0 - 注意:确保物理内存足够,避免过度依赖 Swap。
-
时钟源(Clocksource)
- 问题:某些虚拟化环境下,默认的时钟源可能导致时间轮询开销大,影响定时任务和高精度计时。
- 配置:查看
/sys/devices/system/clocksource/clocksource0/current_clocksource,通常推荐tsc或hpet(视具体云厂商底层硬件而定)。
-
TCP 网络优化
- 问题:高并发场景下,TCP 端口耗尽或连接建立慢。
- 关键参数:
net.ipv4.tcp_tw_reuse = 1:允许 TIME_WAIT 状态的 socket 重新用于新的客户端连接(注意:需配合tcp_timestamps使用,且仅适用于客户端侧,服务端慎用,但现代内核已优化,通常可开启)。net.ipv4.ip_local_port_range:扩大 ephemeral port 范围,防止端口耗尽。net.core.somaxconn:增大监听队列长度,应对突发流量。
二、 JVM 核心调优(引擎)
这是最核心的部分。不要盲目套用网上“万能参数”,必须基于你的业务类型(CPU 密集型 vs IO 密集型)和服务器资源。
-
堆内存设置
-Xms和-Xmx必须相等。避免动态扩容带来的抖动。- 一般建议为服务器物理内存的 50%-70%(预留空间给 OS 缓存、堆外内存等)。
- 示例:
-Xms4g -Xmx4g
-
垃圾收集器选择(GC)
- G1 GC:目前大多数通用场景的首选。适合大堆(>4GB),延迟敏感型应用。
- 参数:
-XX:+UseG1GC - 关键调优:
-XX:MaxGCPauseMillis=200(目标最大停顿时间),-XX:InitiatingHeapOccupancyPercent=45(触发并发标记的阈值)。
- 参数:
- ZGC/Shenandoah:超低停顿(亚毫秒级),适合对延迟极度敏感的场景,但 CPU 开销略高。需要 JDK 11+ 或特定版本。
- CMS:已废弃,不再建议使用。
- G1 GC:目前大多数通用场景的首选。适合大堆(>4GB),延迟敏感型应用。
-
元空间(Metaspace)
-XX:MetaspaceSize和-XX:MaxMetaspaceSize应设置合理上限,防止因类加载过多导致 OOM。- 对于 Spring Boot 等重型框架,可能需要较大的 Metaspace。
-
线程栈大小
-Xss:默认通常是 1MB。如果应用创建大量线程(如 NIO 线程池 + 业务线程),可适当减小至 256k 或 512k,以节省内存。但过深递归可能导致 StackOverflowError。
-
安全与调试参数
- 生产环境务必关闭调试端口:
-Dcom.sun.management.jmxremote.port=(不暴露) 或使用防火墙限制 JMX 访问。 - 启用 GC 日志:
-Xlog:gc*:file=/path/to/gc.log:time,uptime,level,tags:filecount=5,filesize=10M(JDK 9+)。这是排查问题的第一手资料。
- 生产环境务必关闭调试端口:
三、 应用层配置(骨架)
-
时区统一
- 致命坑:容器化或某些 Linux 发行版默认时区可能是 UTC,而业务逻辑依赖 CST(中国标准时间)。
- 解决:启动时指定
-Duser.timezone=Asia/Shanghai,或在 Dockerfile/环境变量中设置TZ=Asia/Shanghai。
-
字符集编码
- 确保 JVM 默认编码为 UTF-8:
-Dfile.encoding=UTF-8。虽然 JDK 9+ 默认已是 UTF-8,但显式声明更稳妥,避免与旧库或外部数据交互时的乱码问题。
- 确保 JVM 默认编码为 UTF-8:
-
Spring Boot 特定配置
- 虚拟线程(Virtual Threads):如果使用 JDK 21+ 和 Spring Boot 3.2+,可考虑启用虚拟线程处理 IO 密集型任务,大幅提升吞吐量。
- 健康检查:正确配置 Actuator 的健康端点,供 K8s 或负载均衡器探测。
- Profile 隔离:严格区分 dev/test/prod 配置文件,严禁在生产环境开启 debug 日志级别。
-
日志框架
- 使用 Logback 或 Log4j2,配置异步 Appender(AsyncAppender),避免 I/O 阻塞主业务线程。
- 日志滚动策略:按天或按大小滚动,保留一定天数,定期清理,防止磁盘写满。
四、 运维与安全(护城河)
-
非 Root 运行
- 安全红线:绝对不要用 root 用户运行 Java 应用。创建一个专用用户(如
appuser),并最小化其权限。 - 示例:
runuser -u appuser -- java -jar app.jar
- 安全红线:绝对不要用 root 用户运行 Java 应用。创建一个专用用户(如
-
资源隔离(Cgroups)
- 如果在裸金属服务器上部署多实例,使用 systemd 的
MemoryLimit、CPUQuota等 cgroup 功能进行资源隔离,防止某个应用吃光所有资源。 - 在 K8s 环境中,通过
resources.requests和resources.limits声明资源。
- 如果在裸金属服务器上部署多实例,使用 systemd 的
-
优雅停机(Graceful Shutdown)
- 配置 Spring Boot 的
server.shutdown=graceful。 - 确保应用能正确处理 SIGTERM 信号,完成正在进行的请求后再退出。否则会导致前端负载均衡器认为服务宕机,重试请求打到已停止的实例上,造成报错。
- 配置 Spring Boot 的
-
安全更新
- 定期更新 JDK 版本,修复已知漏洞(如 Log4j2、Fastjson 等历史漏洞)。
- 使用镜像签名或软件包签名验证,防止供应链攻击。
五、 常见误区提醒
- 误区 1:“内存越大越好”。错!堆内存过大导致 GC 停顿时间变长。应根据 RT(响应时间)要求反推堆大小。
- 误区 2:“忽略 GC 日志”。错!GC 日志是 JVM 健康的体检报告。没有日志,你就是在盲飞。
- 误区 3:“直接复制别人的 JVM 参数”。错!不同业务负载(CPU vs IO)、不同 JDK 版本、不同服务器规格,最优参数完全不同。必须通过压测工具(如 JMeter、Wrk)结合 GC 日志进行调优。
总结 checklist
部署前,请逐项核对:
- [ ] 系统
nofile和swappiness是否调整? - [ ] JVM
-Xms和-Xmx是否一致? - [ ] GC 日志是否开启并配置了轮转?
- [ ] 时区是否为
Asia/Shanghai? - [ ] 是否以非 root 用户运行?
- [ ] 是否配置了优雅停机?
- [ ] 防火墙是否只开放必要端口(如 8080)?
最后,记住:没有银弹。最好的配置来自你对业务的理解和持续的监控反馈。建议搭建完善的 Prometheus + Grafana 监控体系,重点关注 JVM Heap Usage、GC Pause Time、Thread Count 和 System Load 等指标,用数据驱动调优。
云技术博客