跳转至

JVM 核心参数与系统影响

给 Java 服务配置资源时,我先算进程总内存,再确定堆大小,最后根据 GC 日志调整。最先掌握的是 -Xms-Xmx-Xss、容器内存比例、GC 选择和故障日志参数。

下面以 Linux、HotSpot JDK 17/21 为主要范围;日志示例适用于 JDK 9+。JDK 8 的 GC 日志语法不同,不要混用。

先理解内存花在哪里

Java 进程内存
├── Heap:业务对象、数组、缓存                  → -Xms / -Xmx
├── Metaspace:类元数据                        → MaxMetaspaceSize
├── 平台线程栈:调用栈、局部变量                → -Xss × 线程数
├── Direct Memory:NIO 缓冲区                  → MaxDirectMemorySize
└── JIT 代码缓存、GC 数据结构、JNI 等本地内存

容器还可能计入文件页缓存等内存,所以 -Xmx4g 只表示堆最大为 4 GiB,进程和容器实际占用可以超过它。内存要同时看 Heap、进程 RSS、容器用量及限制。虚拟地址预留、已提交内存和实际驻留内存也不是同一个指标。

最需要掌握的参数

参数示例 含义 设大或设小,对系统有什么影响
-Xms1g 初始堆大小 较大可减少运行中的堆扩容;同时抬高初始内存预算。它不等于启动瞬间的 RSS
-Xmx2g 最大堆大小 太小会频繁 GC 或 Heap OOM;太大挤压堆外与其他进程,遇到全堆回收时也可能增加停顿成本
-Xss1m 每个平台线程的栈大小 太小容易深层调用 StackOverflowError;太大则降低可创建线程数量,并增加本地内存压力
-XX:MaxRAMPercentage=60 按 JVM 识别的可用内存计算最大堆 适合不同容器规格;识别到的限制必须正确。显式配置 -Xmx 时优先用固定堆上限
-XX:+UseG1GC 选择 G1 回收器 平衡吞吐和停顿;仍有 Stop-The-World 阶段,也需要并发 GC 的 CPU 资源
-XX:MaxGCPauseMillis=200 G1 的期望暂停目标,单位毫秒 更低目标可能增加回收频率和 CPU 成本、降低吞吐;这是软目标,不能保证每次低于 200 ms

-Xms-Xmx 设相同是稳定负载服务器的一种选择,可减少堆大小变化;共享主机、小容器或负载波动大的服务,应结合实际占用考虑初始值。堆变大不能修复内存泄漏,只会延后耗尽时间。

例如有 500 个平台线程,每个栈上限 1 MiB,仅栈的容量预算就约 500 MiB;实际驻留通常不是一次性达到这个数,还要加线程自身的其他开销。JDK 21 虚拟线程不按这个公式逐个预留平台线程栈,其运行还依赖载体线程和堆中数据。

堆外内存参数

参数 作用 运维判断
-XX:MaxMetaspaceSize=256m 限制类元数据空间 过小会触发类元数据相关 GC,最终可能 OutOfMemoryError: Metaspace;需按实际类加载量确定
-XX:MetaspaceSize=128m 初次触发元数据相关 GC 的阈值,之后会自适应 它不是 Metaspace 的初始固定分配量,也不是最大值
-XX:MaxDirectMemorySize=512m 限制受 JVM 管理的 NIO 直接缓冲区容量 太小可能出现 Direct buffer memory;太大增加容器内存风险。不能用它限制所有 JNI/第三方本地分配
-XX:NativeMemoryTracking=summary 记录 JVM 内部原生内存分类 有额外开销,用于定位 Heap 之外的增长;不覆盖所有第三方本地分配

遇到“堆只有 1 GiB,进程却占了 3 GiB”,先检查线程数、直接内存、类元数据和本地库。不要直接把 -Xmx 再扩大。

jcmd <pid> VM.native_memory summary scale=MB
jcmd <pid> VM.native_memory baseline
# 经过一段有代表性的业务负载后比较
jcmd <pid> VM.native_memory summary.diff scale=MB

NMT 需在启动时开启,关注各分类增长及 committed/reserved 的差异。它不能直接代替 RSS 或容器内存监控。Oracle NMT 说明

GC 与 CPU 怎么取舍

回收器 主要目标 资源和业务影响
G1:-XX:+UseG1GC 平衡吞吐与暂停 通用服务可先建立 G1 基线;根据暂停、分配率、回收后占用调优
Parallel:-XX:+UseParallelGC 高吞吐 适合能容忍暂停的批处理;回收暂停可能影响在线请求尾延迟
ZGC:-XX:+UseZGC 更低暂停 更多工作与应用并发执行,需要 CPU 和内存余量;低暂停不代表请求完全没有排队

这些选择互斥。JDK 21 的分代 ZGC 需要另加 -XX:+ZGenerational;其他主版本的默认行为和可用开关不同,应核对对应 JDK。不要把这一开关直接带入所有版本。

-XX:ParallelGCThreads-XX:ConcGCThreads 分别影响并行、并发 GC 工作线程数。增加线程可能缩短某些回收阶段,但也可能抢走应用 CPU。容器只有 2 核配额时,大量 GC 线程更容易遭遇 CPU throttling,让请求和回收一起变慢。

-XX:ActiveProcessorCount=2 可以覆盖 JVM 识别的处理器数量,影响部分 GC 和线程池的自适应设置;它不是 CPU 限流参数,也不会自动限制所有业务线程池。正常情况下先让 JVM 识别容器资源,再用证据决定是否覆盖。

G1 下不建议一开始固定 -Xmn 或新生代比例,这会约束它根据暂停目标调整年轻代的能力。先看日志,再调整具体参数。

日志与 OOM:上线前配置好

-Xlog:gc*:file=/data/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=20M
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dumps
-XX:+ExitOnOutOfMemoryError

这些是 JVM 启动参数片段,应放在 java 之后、-jar 之前。

  • GC 日志记录回收原因、暂停、回收前后堆容量;轮转控制磁盘增长,长期 debug/trace 级别会增加 I/O。
  • Heap Dump 在 JVM 可处理的相应 OOM 场景保留堆证据,落盘可能造成明显停顿和磁盘压力。目录需提前创建并允许运行用户写入。
  • ExitOnOutOfMemoryError 让进程在相应 OOM 后退出,配合 systemd 或容器重启策略恢复;它本身不会重启服务。

内核直接 OOMKilled 时,JVM 通常来不及生成 Heap Dump。重启后问题反复出现,要检查限制、泄漏和负载;Dump 可能包含业务数据,应限制访问并管理保留时间。

一个 4 GiB 容器的预算例子

以下是用于压测的起点,不是所有 Spring 应用的通用标准:

部分 示例预算
Heap 上限 2 GiB
Metaspace 预算 约 256 MiB,按观测修正
Direct Buffer 预算 约 512 MiB,按框架使用方式修正
平台线程栈 200 个线程 × 1 MiB ≈ 200 MiB 容量预算
剩余 供 JIT、GC、本地库、页缓存等使用,并留峰值余量
java \
  -Xms1g -Xmx2g -Xss1m \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/dumps \
  -XX:+ExitOnOutOfMemoryError \
  '-Xlog:gc*:file=/data/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=20M' \
  -jar /app/app.jar

表中的预算不是对所有内存分类都设置了硬限制。是否增加 Metaspace/Direct Memory 上限,要根据观测、框架及峰值负载决定。

如果希望随容器规格调整堆,可把 -Xms1g -Xmx2g 替换-XX:InitialRAMPercentage=25 -XX:MaxRAMPercentage=50;先确认 JVM 确实识别到容器限制。内存限制还需在容器平台独立配置,JVM 参数不能替代它。

改完后检查什么

jcmd <pid> VM.version
jcmd <pid> VM.command_line
jcmd <pid> VM.flags
jcmd <pid> GC.heap_info

先确认真正运行的进程拿到了参数,再在相同负载下比较 GC 暂停、GC 时间占比、回收后占用、RSS、容器内存、CPU throttling、接口 P95 和错误率。

观测 调整方向
堆持续逼近上限且频繁回收 区分合理存活对象、分配过快和泄漏;有总内存余量才考虑增堆
堆不高但容器逼近限制 查线程栈、直接内存、类加载和本地库;必要时缩小堆给堆外留空间
GC 暂停超标 查 GC 原因、存活量、大对象和 CPU 配额;再考虑 G1 目标或其他 GC
CPU 高但 GC 时间少 转向业务热点、锁、序列化等线程证据
出现 StackOverflowError 查递归与调用深度,再评估 -Xss;增大栈有线程内存成本

继续取证见 JVM 与 Spring 排障。参数定义见 JDK 21 java 命令,ZGC 的内存余量与版本行为见 JDK 21 ZGC