这几天线上实例会偶尔报警重启,查了一下问题,偶然发现了一个 java
命令参数顺序的坑。
我线上的启动命令行为:
java -jar app.jar -DcustomArgs=someCustomArgs -Xmx2048M -Xms2048M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+ParallelRefProcEnabled -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/log/hs_err_pid%p.log -Xloggc:/log/gc.log -XX:HeapDumpPath=/log 复制代码
这个命令看起来没什么问题,设置了JVM参数,GC日志打印等等,看起来一切OK。但是,实际上,当线上出现重启后,我去查看gc日志,却发现,压根没有gc日志的文件,我想了想,应当是我设置的参数没有生效,于是用 jps -lvm
查看,结果如下:
448 app.jar -DcustomArgs=someCustomArgs -Xmx2048M -Xms2048M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+ParallelRefProcEnabled -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=/log/hs_err_pid%p.log -Xloggc:/log/gc.log -XX:HeapDumpPath=/log 复制代码
看起来没问题啊,于是我又看了看 java
命令的操作手册,发现了问题:
java -h 用法: java [-options] class [args...] (执行类) 或 java [-options] -jar jarfile [args...] (执行 jar 文件) 其中选项包括: -d32 使用 32 位数据模型 (如果可用) -d64 使用 64 位数据模型 (如果可用) -server 选择 "server" VM 默认 VM 是 server, 因为您是在服务器类计算机上运行。 -cp <目录和 zip/jar 文件的类搜索路径> -classpath <目录和 zip/jar 文件的类搜索路径> 用 : 分隔的目录, JAR 档案 和 ZIP 档案列表, 用于搜索类文件。 -D<名称>=<值> 设置系统属性 -verbose:[class|gc|jni] 启用详细输出 -version 输出产品版本并退出 -version:<值> 警告: 此功能已过时, 将在 未来发行版中删除。 需要指定的版本才能运行 -showversion 输出产品版本并继续 -jre-restrict-search | -no-jre-restrict-search 警告: 此功能已过时, 将在 未来发行版中删除。 在版本搜索中包括/排除用户专用 JRE -? -help 输出此帮助消息 -X 输出非标准选项的帮助 -ea[:<packagename>...|:<classname>] -enableassertions[:<packagename>...|:<classname>] 按指定的粒度启用断言 -da[:<packagename>...|:<classname>] -disableassertions[:<packagename>...|:<classname>] 禁用具有指定粒度的断言 -esa | -enablesystemassertions 启用系统断言 -dsa | -disablesystemassertions 禁用系统断言 -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<选项>] 按完整路径名加载本机代理库 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument -splash:<imagepath> 使用指定的图像显示启动屏幕 有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。 复制代码
注意这两点: java [-options] -jar jarfile [args...]
和 -X 输出非标准选项的帮助
也就是说, java
这个命令行后边的 [-options]
是在 -jar jarfile
前面的,我那个命令行里,正好搞反了,我的命令行里的一堆 -X
或者 -XX
应当都属于 [-options]
里面的,这一点可用 java -X
验证:
-Xmixed 混合模式执行 (默认) -Xint 仅解释模式执行 -Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件> 设置搜索路径以引导类和资源 -Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件> 附加在引导类路径末尾 -Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件> 置于引导类路径之前 -Xdiag 显示附加诊断消息 -Xnoclassgc 禁用类垃圾收集 -Xincgc 启用增量垃圾收集 -Xloggc:<file> 将 GC 状态记录在文件中 (带时间戳) -Xbatch 禁用后台编译 -Xms<size> 设置初始 Java 堆大小 -Xmx<size> 设置最大 Java 堆大小 -Xss<size> 设置 Java 线程堆栈大小 -Xprof 输出 cpu 配置文件数据 -Xfuture 启用最严格的检查, 预期将来的默认值 -Xrs 减少 Java/VM 对操作系统信号的使用 (请参阅文档) -Xcheck:jni 对 JNI 函数执行其他检查 -Xshare:off 不尝试使用共享类数据 -Xshare:auto 在可能的情况下使用共享类数据 (默认) -Xshare:on 要求使用共享类数据, 否则将失败。 -XshowSettings 显示所有设置并继续 -XshowSettings:all 显示所有设置并继续 -XshowSettings:vm 显示所有与 vm 相关的设置并继续 -XshowSettings:properties 显示所有属性设置并继续 -XshowSettings:locale 显示所有与区域设置相关的设置并继续 -X 选项是非标准选项, 如有更改, 恕不另行通知。 复制代码
也就是说,我因为搞反了 java
中参数的顺序,导致了 java
忽略掉了我的参数。
那么,如何看到当前JVM真实应用了的参数呢,我查了一下,发现可以用 jinfo -flags <pid>
或 java -XX:+PrintFlagsFinal
或 java -XX:+PrintCommandLineFlags
来查看。
运行 jinfo -flags <pid>
会有以下输出:
Attaching to process ID 560, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.66-b17 Non-default VM flags: -XX:CICompilerCount=15 -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=32210157568 -XX:MaxNewSize=10736369664 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=715653120 -XX:OldSize=1431830528 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC Command line: 复制代码
可以看到 Non-default
中 MaxHeapSize
和 MaxNewSize
都是大的离谱的数 32G
和 10G
,这两个数是怎么来的我暂时没找到来源点(猜测是运维平台自动附加的?),同时,可以看到 Command line:
这里是空的,也就是没有应用命令行中的设置参数。
我们再来运行 java -XX:+PrintCommandLineFlags
,结果如下:
-XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=32210157568 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 复制代码
可以看到,在命令行上,应用了参数,参数中设置了 MaxHeapSize
和 MaxNewSize
为大的离谱的数 32G
和 10G
,当然,这个并不是我设置的,估计是一些其他的脚本设置的(猜测,尚未找到源头)。
这个算是个不大不小的坑(这个坑和线上问题有没有关联尚未验证,总之是个坑),以后需要注意。
Java的Xms和Xmx最小可以是多少
JVM中的MaxHeapSize值不一致问题
JVM实用参数(三)打印所有XX参数及值
JVM系列第12讲:JVM参数之查看JVM参数
jinfo命令详解
what's means of jinfo non-default vm flags
感谢 @qixiaobo 的更正,前文提到的那个离谱的32G,应当是在没有明确指定 -Xmx
时,JVM根据策略自动计算出的堆最大的大小,然后那个10G的新生代大小同样也是依据于策略算出来的,关于其策略可参考官方的Update Release Notes:
还可参考:
As of JDK6U18 following are configurations for the Heap Size.
In the Client JVM, the default Java heap configuration has been modified to improve the performance of today's rich client applications. Initial and maximum heap sizes are larger and settings related to generational garbage collection are better tuned.
The default maximum heap size is half of the physical memory up to a physical memory size of 192 megabytes and otherwise one fourth of the physical memory up to a physical memory size of 1 gigabyte. For example, if your machine has 128 megabytes of physical memory, then the maximum heap size is 64 megabytes, and greater than or equal to 1 gigabyte of physical memory results in a maximum heap size of 256 megabytes. The maximum heap size is not actually used by the JVM unless your program creates enough objects to require it. A much smaller amount, termed the initial heap size, is allocated during JVM initialization. This amount is at least 8 megabytes and otherwise 1/64 of physical memory up to a physical memory size of 1 gigabyte.
Source : www.oracle.com/technetwork…
To answer this question it's critical whether the Java VM is in CLIENT or SERVER mode. You can specify "-client" or "-server" options. Otherwise java uses internal rules; basically win32 is always client and Linux is always server, but see the table here:
docs.oracle.com/javase/6/do…
Sun/Oracle jre6u18 doc says re client: the VM gets 1/2 of physical memory if machine has <= 192MB; 1/4 of memory if machine has <= 1Gb; max 256Mb. In my test on a 32bit WindowsXP system with 2Gb phys mem, Java allocated 256Mb, which agrees with the doc.
Sun/Oracle jre6u18 doc says re server: same as client, then adds confusing language: for 32bit JVM the default max is 1Gb, and for 64 bit JVM the default is 32Gb. In my test on a 64bit linux machine with 8Gb physical, Java allocates 2Gb, which is 1/4 of physical; on a 64bit linux machine with 128Gb physical Java allocates 32Gb, again 1/4 of physical.
Thanks to this SO post for guiding me:
Definition of server-class machine changed recently?
另外,从JDK源码 hotspot/src/share/vm/runtime/arguments.cpp
也可看到相关逻辑(猜测是这里,并未看懂)
void Arguments::set_heap_size() { if (!FLAG_IS_DEFAULT(DefaultMaxRAMFraction)) { // Deprecated flag FLAG_SET_CMDLINE(uintx, MaxRAMFraction, DefaultMaxRAMFraction); } const julong phys_mem = FLAG_IS_DEFAULT(MaxRAM) ? MIN2(os::physical_memory(), (julong)MaxRAM) : (julong)MaxRAM; // If the maximum heap size has not been set with -Xmx, // then set it as fraction of the size of physical memory, // respecting the maximum and minimum sizes of the heap. if (FLAG_IS_DEFAULT(MaxHeapSize)) { julong reasonable_max = phys_mem / MaxRAMFraction; if (phys_mem <= MaxHeapSize * MinRAMFraction) { // Small physical memory, so use a minimum fraction of it for the heap reasonable_max = phys_mem / MinRAMFraction; } else { // Not-small physical memory, so require a heap at least // as large as MaxHeapSize reasonable_max = MAX2(reasonable_max, (julong)MaxHeapSize); } if (!FLAG_IS_DEFAULT(ErgoHeapSizeLimit) && ErgoHeapSizeLimit != 0) { // Limit the heap size to ErgoHeapSizeLimit reasonable_max = MIN2(reasonable_max, (julong)ErgoHeapSizeLimit); } if (UseCompressedOops) { // Limit the heap size to the maximum possible when using compressed oops julong max_coop_heap = (julong)max_heap_for_compressed_oops(); if (HeapBaseMinAddress + MaxHeapSize < max_coop_heap) { // Heap should be above HeapBaseMinAddress to get zero based compressed oops // but it should be not less than default MaxHeapSize. max_coop_heap -= HeapBaseMinAddress; } reasonable_max = MIN2(reasonable_max, max_coop_heap); } reasonable_max = limit_by_allocatable_memory(reasonable_max); if (!FLAG_IS_DEFAULT(InitialHeapSize)) { // An initial heap size was specified on the command line, // so be sure that the maximum size is consistent. Done // after call to limit_by_allocatable_memory because that // method might reduce the allocation size. reasonable_max = MAX2(reasonable_max, (julong)InitialHeapSize); } if (PrintGCDetails && Verbose) { // Cannot use gclog_or_tty yet. tty->print_cr(" Maximum heap size " SIZE_FORMAT, (size_t) reasonable_max); } FLAG_SET_ERGO(uintx, MaxHeapSize, (uintx)reasonable_max); } // If the minimum or initial heap_size have not been set or requested to be set // ergonomically, set them accordingly. if (InitialHeapSize == 0 || min_heap_size() == 0) { julong reasonable_minimum = (julong)(OldSize + NewSize); reasonable_minimum = MIN2(reasonable_minimum, (julong)MaxHeapSize); reasonable_minimum = limit_by_allocatable_memory(reasonable_minimum); if (InitialHeapSize == 0) { julong reasonable_initial = phys_mem / InitialRAMFraction; reasonable_initial = MAX3(reasonable_initial, reasonable_minimum, (julong)min_heap_size()); reasonable_initial = MIN2(reasonable_initial, (julong)MaxHeapSize); reasonable_initial = limit_by_allocatable_memory(reasonable_initial); if (PrintGCDetails && Verbose) { // Cannot use gclog_or_tty yet. tty->print_cr(" Initial heap size " SIZE_FORMAT, (uintx)reasonable_initial); } FLAG_SET_ERGO(uintx, InitialHeapSize, (uintx)reasonable_initial); } // If the minimum heap size has not been set (via -Xms), // synchronize with InitialHeapSize to avoid errors with the default value. if (min_heap_size() == 0) { set_min_heap_size(MIN2((uintx)reasonable_minimum, InitialHeapSize)); if (PrintGCDetails && Verbose) { // Cannot use gclog_or_tty yet. tty->print_cr(" Minimum heap size " SIZE_FORMAT, min_heap_size()); } } } } 复制代码