有的同窗虽然写了一段时间 Java 了,可是对于 JVM 却不太关注。有的同窗说,参数都是团队规定好的,部署的时候也不用我动手,关注它有什么用,并且,JVM 这东西,听上去就感受很神秘很高深的样子,仍是算了吧。java
没错,部署的时候可能用不到你亲自动手,可是出现问题了怎么办,难道不用你解决问题吗,若是对 JVM 了解不够的话,有些问题可能排查起来就很费力,或者根本没法解决。服务器
本篇以 JDK Hotspot 8 为背景,介绍一下 JVM 的经常使用参数。建议你在作一些小项目、小 demo 的时候,也把这些参数加上,加深印象。以个人经验来看,有些知识你刚开始接触的时候会感受很难理解,可是不要紧,万事开头难嘛,知识点都是须要消化时间的。第一天不理解,甚至过了一个月也不理解,可是总有那么一刻,你会忽然有种茅塞顿开的感受,感受一会儿通了。最后内心面感谢本身在多少多少天之前可以开始学习并坚持学习这些知识点。app
只介绍一些经常使用参数,除了这些经常使用参数外,Hotspot 还提供了不少其余的参数,每个都值得考究。jvm
在使用这些参数以前,你须要对 Java 内存模型有必定的了解,了解一下内存模型。ide
仍是要把内存模型图放在这里,方便理解。工具
-Xms: 堆的初始值,例如 -Xmx2048,初始堆大小为 2G性能
-Xmx: 堆的最大值,例如 -Xmx2048M,容许最大堆内存 2G学习
-Xmn: 新生代大小spa
-XX:SurvivorRatio:Eden 区所占比例,默认是 8,也就是 80%,例如 -XX:SurvivorRatio=8.net
最好将 -Xms 和 -Xmx 的值设置成同样的值,这样作是为了防止随着堆空间使用量增长,会动态的调整堆空间大小,有必定的性能损耗,不如开始就设置成相同的值,来规避性能损失。
-Xss:栈空间大小,栈是线程独占的,因此是一个线程使用栈空间的大小,例如 -Xss256K,若是不设置此参数,默认值是 1M,通常来说设置成 256K 就足够了。
-XX:MetaspaceSize:Metaspace 空间初始大小,若是不设置的话,默认是20.79M,这个初始大小是触发首次 Metaspace Full GC 的阈值,例如 -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize:Metaspace 最大值,默认不限制大小,可是线上环境建议设置,例如
-XX:MaxMetaspaceSize=256M
-XX:MinMetaspaceFreeRatio:最小空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,若是空闲比(空闲空间/当前 Metaspace 大小)小于此值,就会触发 Metaspace 扩容。默认值是 40 ,也就是 40%,例如 -XX:MinMetaspaceFreeRatio=40
-XX:MaxMetaspaceFreeRatio:最大空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,若是空闲比(空闲空间/当前 Metaspace 大小)大于此值,就会触发 Metaspace 释放空间。默认值是 70 ,也就是 70%,例如 -XX:MaxMetaspaceFreeRatio=70
建议将 MetaspaceSize 和 MaxMetaspaceSize 设置为一样大小,避免频繁扩容。
简单日志
-verbose:gc 或者 -XX:+PrintGC
日志格式:
[GC (Allocation Failure) 7892K->5646K(19456K), 0.0060442 secs] [GC (Allocation Failure) , 0.0066315 secs] [Full GC (Allocation Failure) 19302K->13646K(19456K), 0.0032698 secs]
详细日志
#打印详细日志 -XX:+PrintGCDetails #打印 GC 的时间点 -XX:+PrintGCDateStamps
日志格式:
2019-11-13T14:06:46.099-0800: [GC (Allocation Failure) 2019-11-13T14:06:46.099-0800: [DefNew (promotion failed) : 9180K->9157K(9216K), 0.0084297 secs]2019-11-13T14:06:46.107-0800: [Tenured: 10145K->10145K(10240K), 0.0035768 secs] 13802K->13646K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0120887 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 2019-11-13T14:06:47.243-0800: [Full GC (Allocation Failure) 2019-11-13T14:06:47.244-0800: [Tenured: 10145K->10145K(10240K), 0.0042686 secs] 19304K->19146K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0043232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
如下几个 GC 日志相关的参数打印的内容比较多,生产环境可选择性开启,大多数时候不须要开启。
GC 先后的堆信息
-XX:+PrintHeapAtGC
{Heap before GC invocations=0 (full 0): def new generation total 9216K, used 7892K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 96% used [0x00000007bec00000, 0x00000007bf3b5200, xxx.... class space used 445K, capacity 462K, committed 512K, reserved 1048576K Heap after GC invocations=1 (full 0): def new generation total 9216K, used 1023K [0x00000007bec00000, xxx... Metaspace used 3892K, capacity 4646K, committed 4864K, reserved 1056768K class space used 445K, capacity 462K, committed 512K, reserved 1048576K }
GC 致使的 Stop the world 时间
-XX:+PrintGCApplicationStoppedTime
Total time for which application threads were stopped: 0.0070384 seconds, Stopping threads took: 0.0000200 seconds
加载类信息
-verbose:class
[Loaded java.net.URLClassLoader$3$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/rt.jar]
GC 先后的类加载状况
-XX:+PrintClassHistogramBeforeFullGC -XX:+PrintClassHistogramAfterFullGC
num #instances #bytes class name ---------------------------------------------- 1: 140 19016264 [B 2: 2853 226256 [C 3: 138 169072 [I 4: 761 86240 java.lang.Class 5: 2850 68400 java.lang.String 6: 660 41024 [Ljava.lang.Object;
日志输出到文件
以上参数配置好以后,默认会输出到控制台或者服务指定的统一日志的位置。可是这里还会有服务的通常性信息日志、错误日志等,都混在一块儿的话会比较乱,因此,通常都会把 jvm 日志单独存放。
#GC 活动日志,根据配置的参数输出内容 -Xloggc:/Users/fengzheng/jvmlog/gc.log #致命错误日志,只有在 jvm 发生崩溃的时候会输出 -XX:ErrorFile=/Users/fengzheng/jvmlog/hs_err_pid%p.log
堆溢出现场保留
有些错误虽然不会致使 jvm 崩溃,可是对于服务而言也是很是严重的,好比stackOverflow、OutOfMemoryError,发生错误后,留存现场信息对分析错误缘由是相当重要的。jvm 提供了保留堆溢出现场的方法,对于 JDK 8 而言,多是 heap 溢出,也多是 Metasapce 溢出。
-XX:HeapDumpPath=/Users/fengzheng/jvmlog -XX:+HeapDumpOnOutOfMemoryError
最后出现异常后,保存的文件格式为 java_pidxxx.hprof,pid 后面是发生溢出的进程 id,以后能够用 VisualVM、JProfiler 等工具打开分析。
随着 JDK 版本的升级,可以使用的垃圾收集器类型也愈来愈多了。JDK 8 可以使用的垃圾收集器有 7 种,固然有点只适用于年轻代,有点只使用于老年代,JDK 8 中最新的垃圾收集器是 G1,能够用于年轻代和老年代。到了 JDK 11,还出了 ZGC。
下图是 JDK 8 中可以使用的垃圾收集器以及它们配合使用的关系。
Serial、ParNew、Parallel Scavenge 只适用于年轻代,CMS、Serial Old、Parallel Old 只适用于老年代,而 G1 通用于年轻代和老年代。连线表示它们之间可配合使用的关系,其中 CMS 和 Serial Old 连线的意思是说 Serial Old 会做为 CMS 的后预案,当 CMS 发生 Concurrent Mode Failure 时启用。
在 JDK 8 中,若是不指定垃圾收集器,默认使用参数 -XX:+UseParallelGC
,新生代使用 Parallel Scavenge,老年代使用 Serial Old。
-XX:+UseSerialGC:使用 Serial + Serial Old ,运行于 client 模式下的默认设置
-XX:+UseConcMarkSweepGC:使用 ParNew+CMS+Serial Old,CMS 垃圾收集器
-XX:+UseParallelGC:Parallel Scavenge + Serial Old,JDK 8 server 模式下的默认设置
-XX:+UseParallelOldGC:Parallel Scavenge + Parallel Old
-XX:+UseG1GC:使用 G1 垃圾收集器
除了日志外,当咱们须要实时查看 JVM 运行状况的时候怎么办,固然能够到 JVM 所在服务器用 jstack、jmap、jinfo 等工具进行查看,可是又不够直观,这时候就须要开启 JMX 远程功能,使用 jConsole、VisualVM 等工具进行监控。或者本身开发监控平台
开启参数以下:
-Dcom.sun.management.jmxremote #指定 jvm 所在服务器 ip 或域名 -Djava.rmi.server.hostname=192.168.1.1 #指定端口 -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.rmi.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false