截止到2020年五月,JVM中仅仅只是关于垃圾回收和内存相关的参数就已经超过600个。如果算上其他方面的参数,JVM相关的总参数能轻松超过1000个。参数太多了,弄得人很懵逼。在这边文章中,我们只选取了7个比较重要,且有用的JVM参数来介绍。
-Xmx 和 -XX:MaxMetaspaceSize
-Xmx可能是最重要最常用的JVM参数了。-Xmx用来定义能分配给应用的最大堆空间大小。你可以像这样使用:
-Xmx2g
堆空间大小直接决定着应用性能。但是随之而来的问题是,对于一个应用,应该设置多大的堆空间是合理的?我应该为我的应用设置一个大的堆空间,还是一个相对较小的?答案是:“看情况”,这个问题,另开一篇文章专门探讨。
这里只提示一点,将-Xmx和-Xms设置为相同,能获得更好的性能。
元空间是对JVM规范中方法区的实现,用于存储JVM中的元数据信息,比如类定义,方法定义等。在默认情况下,元空间使用的是本地内存空间,所以理论上讲,元空间地址是没有上限的,他受限于机器的内存大小,内存寻址空间大小等。可以通过以下参数来指定元空间的大小:
-XX:MaxMetaspaceSize=512m //设置元空间最大空间-XX:MetaspaceSize //初始元空间大小
对于-XX:MetaspaceSize来说,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
另:除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
GC回收器类型
截止目前为止,在OpenJDK中,一共提供了7种类型的GC【这里注意区分一下GC回收器类型和GC回收算法】:
- Serial GC
- Parallel GC
- Concurrent Mark & Sweep GC
- G1 GC
- Shenandoah GC
- Z GC
- Epsilon GC
如果没有特别的指定GC回收器,JVM会使用默认的,到Java8,Parallel GC是默认回收器,从Java9之后,G1 GC是默认的GC回收器。
使用何种GC回收器,对于应用的性能表现起着至关重要的作用。 从测试数据来看,Z GC有非常不错的表现。如果你的应用运行在JVM11以上,我们建议优先考虑使用Z GC(-XX:+UseZGC)。
下表中列出了不同GC算法及其对应的开启参数:
- Serial GC -XX:+UseSerialGC
- Parallel GC -XX:+UseParallelGC
- Concurrent Market & Sweep (CMS) GC -XX:+UseConcMarkSweepGC
- G1 GC -XX:+UseG1GC
- Shenandoah GC -XX:+UseShenandoahGC
- Z GC -XX:+UseZGC
- Epsilon GC -XX:+UseEpsilonGC
Enable GC Logging
GC日志中包含了包括垃圾回收事件,内存空间情况,时间间隔等等。可以使用下面的JVM参数来开启GC日志。
JDK8及其之前:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}
JDK9及之后:
-Xlog:gc*:file={file-path}
一个详细例子:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/workspace/myAppgc.log-Xlog:gc*:file=/opt/workspace/myAppgc.log
一般情况下,GC日志主要用来调整GC性能。然而,GC日志中也包含了很多微观指标。这些指标可用于预测应用程序的可用性和性能。在这里,我们只举一个例子:“GC Throughput(GC吞吐量)”。GC吞吐量是应用程序处理应用事务所花费的时间与处理GC活动所花费的时间之比。比如应用的GC吞吐量是98%,这意味着应用程序将98%的时间花在处理应用活动上,剩下的2%花在GC活动上。
接下来看一个健康的JVM的堆使用图:
你可以看到一个完美的锯齿图案。可以看到到,当运行完整的GC(红色三角形)时,内存利用率下降到了底部。再来看一个有问题的JVM堆使用图:
注意图案的右侧区域,及时GC不断的运行,内存利用率仍然没有明显下降。这就是一个出现了内存问题的典型迹象。
当我们更仔细的分析这个图片,我们可以看到,在8点左右的时间,完全GC开始重复出现,知道8点45左右,应用出现了OOM异常。8点之前,GC的吞吐量在99%左右,但是在8点之后,GC的吞吐量掉到了60%,因为大量重复GC的动作出现,让应用已经没有太多时间处理应用的正常事务。一种主动的措施,如果当发现GC吞吐量开始下降的时候,我们可以从负载平衡群中取出该JVM。这样,出现问题的JVM就不会处理任何新的流量,可以最大限度地减少对客户请求的影响。
可以使用GCeasy REST API实时监控GC的微观数据,也可以使用gcviewer (https://github.com/chewiebug/GCViewer)离线分析GC日志文件。
-XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath
OOM异常严重影响应用的可用性/性能SLA等级。要诊断OOM异常或任何与内存相关的问题,必须在应用程序开始遇到OOM之前的某一时刻或几分钟捕获heap dump(堆转储文件)。由于我们不知道OOM何时出来,所以很难手动捕获它。一般通过传递以下JVM参数来自动捕获heap dump:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={HEAP-DUMP-FILE-PATH}
在-XX:HeapDumpPath中,我们设置一个堆转储文件地址。当这两个参数传递给JVM之后,当抛出OOM的时候,heap dump会被自动捕获并存储在指定文件路径上。
一般可以使用jhat、EclipseMAT分析heap dump。
-Xss
每个应用在运行时都会产生大量线程,每个线程拥有自己的栈空间。在每个线程栈中包含了以下内容:
- 当前正在执行的方法
- 原始数据类型
- 变量
- 对象指针
- 返回值
每一项都占用内存。如果内存占用超过一定限制,就会抛出StackOverflowError异常。可以使用-Xss参数来调整线程栈的大小。例如:
-Xss256k
如果设置-Xss过大,会造成内存的阻塞和浪费。例如,假设我们设置-Xss大小为2MB,但实际上,只需要256KB,就会造成非常大的内存浪费,而不仅仅只是字面量上的1792KB。假设应用一共有500个线程,当-Xss设置为2MB,线程会总共占用1000MB内存。而如果设置-Xss为256KB,实际总共只需要125MB内存空间,总共节约了875MB内存。所以,-Xss的设置会造成巨大的内存消耗差异。
我们建议先将-Xss设置为一个较小的值,比如256KB,并使用该设置完成回归、性能和AB测试。如果在这个过程中遇到了StackOverflowError,再逐步提高该值即可,否则使用一个较小的值。
-Dsun.net.client.defaultConnectTimeout 和 -Dsun.net.client.defaultReadTimeout
在一个应用中,常常会涉及到使用各种协议(SOAP, REST, HTTP, HTTPS, JDBC, RMI等)和第三方应用交互。有时候第三方应用会响应很慢,或者无响应。
在这种情况下,如果没有一个合理的超时时间设置,如果远端应用没法及时响应,则会造成我们的应用线程/资源出现问题。远程应用程序无响应会影响我们程序的可用性。所以,设置合理的超时时间是非常必要的。
你可以通过设置这两个非常强大的网络参数,在JVM层面上控制所有通过java.net.URLConnection建立的协议连接。
sun.net.client.defaultConnectTimeout:设置连接到主机的超时时间(毫秒)sun.net.client.defaultReadTimeout:与资源建立连接时从输入流读取数据的超时时间(毫秒)比如:
-Dsun.net.client.defaultConnectTimeout=2000-Dsun.net.client.defaultReadTimeout=2000
如果设置为-1,则表示不超时。
原文链接:https://www.javacodegeeks.com/2020/03/7-jvm-arguments-of-highly-effective-applications.html