JVM参数分类

jvm 参数可分为三类:

  • 标准参数:以 “-“ 开头的参数
  • 非标准参数:以 “-X“ 开头的参数
  • 不稳定参数:以”-XX“ 开头的参数

标准参数

标准参数是指在各个JVM版本中基本保持不变,相对比较稳定。

标准参数统一都是以 “-“ 开头,如:

1
java -classpath E:/code -Dprofile=dev HelloWorld tom jack

注意:其中HelloWorld 是被运行的 HelloWorld.class。HelloWorld 之前就是设置的JVM标准参数(-classpath、-D),HelloWorld 之后的参数(tom、jack)是用来传给 main(String[] args) 方法的args数组变量的,两者位置不要放错。

查看所有标准参数

打开一个命令终端,执行 java -help,就可以展示所有的JVM标准参数。

非标准参数

非标准参数表示不保证所有JVM实现都支持这些参数,在将来的JVM版本中可能会发生改变。非标准参数统一以 -X 开头,如 -Xmx20M 设置最大java堆大小,示例:

1
java -classpath E:/code -Dfile.encoding=UTF-8 -Dprofile=dev -Xmx20M HelloWorld tom jack

查看所有非标准参数

打开一个命令终端,执行 java -X,就可以展示所有的JVM非标准参数。

不稳定参数

不稳定参数这是我们日常开发中接触到最多的参数类型,也是非标准化参数,相对来说不稳定,随着JVM版本的变化可能会发生变化,主要用于JVM调优和debug。

不稳定参数统一以 “-XX“ 开头,书写格式分为两种:

  • bool 类型:
    • -XX:+<option>:代表启用 true
    • -XX:-<option>:代表禁用 false
  • 数值或字符串类型:
    • -XX:<option>=<number>:数字如果有单位一般是 兆字节的“ m”或“ M”,千字节的“ k”或“ K”以及千兆字节的“ g”或“ G”(例如32k与32768相同)
    • -XX:<option>=<string>:字符串通常用于指定文件,路径或命令列表

如打印GC日志 -XX:+PrintGCDetails、设置对象最大晋升老年代的年龄 -XX:MaxTenuringThreshold=15,示例:

1
java -classpath E:/code -Dfile.encoding=UTF-8 -Dprofile=dev -Xmx20M -XX:+PrintGCDetails -XX:MaxTenuringThreshold=15  HelloWorld tom jack

查看所有不稳定参数

执行命令终端,执行 -XX:+PrintFlagsFinal,展示所有不稳定参数。

  • 第一列:参数类型
  • 第二列:参数名称
  • 第三列:”=“ 表示第四列是初始值,”:=“表示参数被用户或者JVM赋值了
  • 第四列:参数值
  • 第五列:参数类别

查看所有参数初始值

执行参数-XX:+PrintFlagsInitial。相对于-XX:+PrintFlagsFinal 参数,该参数打印出的结果中第四列都是初始值。

打印已经被用户或者当前虚拟机设置过的参数

执行参数-XX:+PrintCommandLineFlags。相当于列举出 -XX:+PrintFlagsFinal的结果中所有第三列是”:=“的参数。一般运行程序时,最好都加上该参数,可以知道该程序运行都设置过哪些JVM参数。

JVM参数配置说明

调优栈内存

堆大小典型配置参数

  • -Xmx:设置最大堆大小。

    -Xmx3550m,设置JVM最大可用内存为3550 MB。

  • -Xms:设置JVM初始内存。

    -Xms3550m,设置JVM初始内存为3550 MB。此值建议与-Xmx相同,避免每次垃圾回收完成后JVM重新分配内存。

  • -Xmn2g:设置年轻代大小。

    -Xmn2g,设置年轻代大小为2 GB。整个JVM内存大小=年轻代大小+年老代大小+持久代大小。持久代一般固定大小为64 MB,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

  • -Xss:设置线程的栈大小。

    -Xss128k,设置每个线程的栈大小为128 KB。

    说明:JDK 5.0版本以后每个线程栈大小为1 MB,JDK 5.0以前版本每个线程栈大小为256 KB。请依据应用的线程所需内存大小进行调整。在相同物理内存下,减小该值可以生成更多的线程。但是操作系统对一个进程内的线程个数有一定的限制,无法无限生成,一般在3000个~5000个。

  • -XX:NewRatio=n:设置年轻代和年老代的比值。

    -XX:NewRatio=4,设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。如果设置为4,那么年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。

  • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。

    -XX:SurvivorRatio=4,设置年轻代中Eden区与Survivor区的大小比值。如果设置为4,那么两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6。

  • -XX:MaxPermSize=n:设置持久代大小。

    -XX:MaxPermSize=16m,设置持久代大小为16 MB。

  • -XX:MaxTenuringThreshold=n:设置垃圾最大年龄。

    -XX:MaxTenuringThreshold=0,设置垃圾最大年龄。如果设置为0,那么年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,提高了效率。如果将此值设置为较大值,那么年轻代对象会在Survivor区进行多次复制,增加了对象在年轻代的存活时间,增加在年轻代即被回收的概率。

调优回收器GC

吞吐量优先的GC典型配置参数

  • -XX:+UseParallelGC:选择垃圾收集器为并行收集器。

    -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20-XX:+UseParallelGC此配置仅对年轻代有效,即在示例配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。

  • -XX:ParallelGCThreads:配置并行收集器的线程数,即同时多少个线程一起进行垃圾回收。此值建议配置与处理器数目相等

    -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20-XX:ParallelGCThreads=20表示配置并行收集器的线程数为20个。

  • -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC-XX:+UseParallelOldGC表示对年老代进行并行收集。

    说明:JDK 6.0支持对年老代并行收集。

  • -XX:MaxGCPauseMillis:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100-XX:MaxGCPauseMillis=100设置每次年轻代垃圾回收的最长时间为100 ms。

  • -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时该间或者收集频率,该值建议使用并行收集器时,并且一直打开。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy

响应时间优先的GC典型配置参数

  • -XX:+UseConcMarkSweepGC:设置年老代为并发收集。配置了-XX:+UseConcMarkSweepGC,建议年轻代大小使用-Xmn设置。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

  • -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。

    说明:JDK 5.0以上版本,JVM根据系统配置自行设置,无需再设置此值。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

  • -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction=5,表示运行GC5次后对内存空间进行压缩、整理。

  • -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。该值可能会影响性能,但是可以消除碎片。

    -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

用于辅助的GC典型配置参数

  • -XX:+PrintGC:用于输出GC日志。
  • -XX:+PrintGCDetails:用于输出GC日志。
  • -XX:+PrintGCTimeStamps:用于输出GC时间戳(JVM启动到当前日期的总时长的时间戳形式)。示例如下:0.855: [GC (Allocation Failure) [PSYoungGen: 33280K->5118K(38400K)] 33280K->5663K(125952K), 0.0067629 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
  • -XX:+PrintGCDateStamps:用于输出GC时间戳(日期形式)。示例如下:2022-01-27T16:22:20.885+0800: 0.299: [GC pause (G1 Evacuation Pause) (young), 0.0036685 secs]
  • -XX:+PrintHeapAtGC:在进行GC前后打印出堆的信息。
  • -Xloggc:../logs/gc.log:日志文件的输出路径。

JVM内存配置最佳实践

当设置的JVM堆空间过小时,程序会出现系统内存不足OOM(Out of Memory)的问题。特别是在容器环境下,不合理的JVM堆参数设置会导致各种异常现象产生,例如应用堆大小还未到达设置阈值或规格限制,就因为OOM导致重启等。

通过MaxRAMPercentage限制堆大小(推荐)

在容器环境下,Java只能获取服务器的配置,无法感知容器内存限制。可以通过设置-Xmx来限制JVM堆大小,但该方式存在以下问题:

  • 当规格大小调整后,需要重新设置堆大小参数。
  • 当参数设置不合理时,会出现应用堆大小未达到阈值但容器OOM被强制关闭的情况。

应用程序出现OOM问题时,会触发Linux内核的OOM Killer机制。该机制能够监控占用过大内存,尤其是瞬间消耗大量内存的进程,然后它会强制关闭某项进程以腾出内存留给系统,避免系统立刻崩溃。

推荐的JVM参数设置:

1
-XX:+UseContainerSupport -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof

参数说明如下:

参数 说明
-XX:+UseContainerSupport 使用容器内存。允许JVM从主机读取cgroup限制,例如可用的CPU和RAM,并进行相应的配置。当容器超过内存限制时,会抛出OOM异常,而不是强制关闭容器。
-XX:MaxRAMPercentage 设置JVM使用容器内存百分比。由于存在系统组件开销,建议最大不超过75.0,推荐设置为70.0。
-XX:+PrintGCDetails 输出GC详细信息。
-XX:+PrintGCDateStamps 输出GC时间戳。日期形式,例如2019-12-24T21:53:59.234+0800。
-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log GC日志文件路径。需保证Log文件所在容器路径已存在,建议您将该容器路径挂载到NAS目录,以便自动创建目录以及实现日志的持久化存储。
-XX:+HeapDumpOnOutOfMemoryError JVM发生OOM时,自动生成DUMP文件。
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof DUMP文件路径。需保证DUMP文件所在容器路径已存在,建议您将该容器路径挂载到NAS目录,以便自动创建目录以及实现日志的持久化存储。
  • 该特性支持JDK 8u191+、JDK 10及以上版本。
  • 如果您没有将文件挂载到NAS目录,必须保证/home/admin/nas路径存在,否则不会产生日志。

通过Xms Xmx限制堆大小

可以通过设置-Xms-Xmx来限制堆大小,推荐的JVM参数设置:

1
-Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof

参数说明如下:

参数 说明
-Xms 设置JVM初始内存大小。建议与-Xmx相同,避免每次垃圾回收完成后JVM重新分配内存。
-Xmx 设置JVM最大可用内存大小。为避免容器OOM,请为系统预留足够的内存大小。

推荐的堆大小设置:

内存规格大小 JVM堆大小
1 GB 600 MB
2 GB 1434 MB
4 GB 2867 MB
8 GB 5734 MB

常见问题

  1. 容器出现137退出码的含义是什么?

    当容器使用内存超过限制时,会出现容器OOM,导致容器被强制关闭。此时业务应用内存可能并未达到JVM堆大小上限,所以不会产生Dump日志。建议您调小JVM堆大小的上限,为容器内其他系统组件预留足够多的内存空间。

  2. 堆大小和规格内存的参数值可以相同吗?

    不可以。因为系统自身组件存在内存开销,例如使用SLS进行日志收集时会占用一小部分的内存空间,所以不能将JVM堆大小设置为和规格内存大小相同的数值,需要为这些系统组件预留足够的内存空间。

  3. 在JDK 8版本下设置-XX:MaxRAMPercentage值为整数时报错怎么处理?

    这是JDK 8的一个Bug。具体信息,请参见Java Bug Database。例如,在JDK 8u191版本下,设置-XX:MaxRAMPercentage=70,这时JVM会启动报错。


    解决方案如下:

    • 方式一:设置-XX:MaxRAMPercentage70.0-XX:InitialRAMPercentage-XX:MinRAMPercentage参数值同样不可设置为整数,需按照浮点数形式来设置。
    • 方式二:升级JDK版本至JDK 10及以上版本。