JDK17的GC调优
基于JDK17优化JVM内存
主要分为堆内存和非堆内存
堆内存
-
-Xms : 设置堆内存的初始大小。
默认单位是bytes。这个值必须是1024的整数,并且必须大于1M。
可以在数字后面加k或者K表示KB,m或者M表示MB,g或者G表示GB。例如 -Xms62991456 , -Xms6144k, -Xms6m。 -
-Xmx : 设置堆内存的最大大小。
配置方式和-Xms一样。只不过,他的默认值可以在运行时基于操作系统自行决定。
在服务端进行部署时,通常将-Xms和-Xmx设置成相同的值,减少JAVA应用在运行过程中的临时内存申请行为。
如果内存资源比较紧张,那就需要JVM能够按需索取内存。先申请一小部分内存,内存不够了,再申请一部分新的内存空间。
-XX:MinHeapFreeRatio -XX:MinHeapSize: 设置一次GC后所允许的堆空间的最小值。如果剩余的堆空间降落到这个阈值之下,这时堆空间就会启动一次扩充。这两个参数一个是比例,一个是大小。
非堆内存
元空间
MetaSpace元空间主要存储的是JAVA类的一些元信息。与永久代不同的是,MetaSpace并不使用JVM的内存,而是直接使用本地内存,这意味着Metaspace的大小不再受限于JVM内存大小的限制,而是受操作系统可用内存的限制。
如果本地内存耗尽,还是会导致OutOfMemmoryError异常的。
- -XX:MetaspaceSize : 元空间大小
这里设置的并不是元空间具体的大小,而是当元空间大小超过这个阈值时,就会触发一次GC。而在后续运行过程中,触发GC的阈值会根据元空间的使用情况进行自动调整到一个稳定的阈值。元空间的默认值取决于平台。
每次gc如果空间仍然不够就会每次扩充或者缩小空间
- -XX:MaxMetaspaceSize : 元空间最大值
设置元空间大小的最大值。默认元空间是无限制的。元空间的大小取决于应用本身以及操作系统可提供的内存大小。一个应用程序中,元空间内的数据大小是不应该经常发生变动的,设定一个合理的最大值,可以尽早避免一些非正常的元空间数据暴涨对操作系统的影响。
线程栈空间(虚拟机栈)
JAVA进程在运行时,会为每个线程开辟一块内存,用来执行线程中的对应指令。整个内存是一个栈结构,先进后出。
线程中执行的每个方法对应栈空间中的一个方法帧。方法帧中主要包含了程序计数器、操作数栈、局部变量表、返回地址等几个标准部分。
线程栈空间大部分的内存都会随着方法结束而释放,所以通常不需要单独设置。但是如果你的应用中的方法嵌套非常多,或者有很多长期执行的复杂方法,那么就需要调整栈空间大小。
- -Xss : 设置线程栈空间的大小
栈空间默认值大小在Linux和MacOS系统中,都是1024KB。Windows中,则需要依靠配置的虚拟内存大小决定。
-Xss1m, -Xss1024k。
配置栈空间大小,还可以用另外一个参数: -XX:ThreadStackSize。配置方式稍有不同。例如 -XX:ThreadStackSize=1K, -XX:ThreadStackSize=1024k。
热点代码缓存空间
jvm启动时,-server选项表示JVM以服务器模式运行。服务器模式下,HotSpot虚拟机会将执行频率高的热点代码识别出来,提前进行编译,并将编译结果缓存起来。后续执行时,就可以以编译执行的方式直接读取缓存,而不用再一行代码一行代码的进行解释执行。而这些热点代码,就保存在非堆区的CodeSpace中。
-
-XX:InitialCodeCacheSize=size
设定代码缓存空间的初始大小 -
-XX:ReservedCodeCacheSize=size
设定代码缓存空间的最大大小。代码缓冲区最大大小默认值是240MB。如果禁止提前编译(-XX:-TieredCompilation) ,那么最大的大小默认是48MB。如果自己指定,这个值不能比初始值小。 -
-XX:+SegmentedCodeCache 启用代码缓存分割
这是JDK8中没有的一个参数,在JDK17中默认启用。这个参数的作用主要是优化代码缓存空间的内存使用。如果没有打开这个选项,那么所有的代码缓存是一个大的内存片段,这不利于内存空间的灵活使用。
这个机制如果需要生效,还需要启用提前编译-XX:+TieredCompilation 并且-XX:ReservedCodeCacheSize >= 240 MB 。
启用代码缓存分割后,JVM底层就是将代码缓存划分成这三个部分的。- -XX:ProfiledCodeHeapSize=size ;
- -XX:NonNMethodCodeHeapSize=size ;
- -XX:NonProfiledCodeHeapSize=size ;
应用程序类数据共享
Application Class Data Sharing(应用程序列数据共享,简称AppCDS)是一种旨在提高运行相同代码的多个JVM的启动时间,并减少他们的内存占用的一种优化机制。
使用AppCDS机制,可以在JVM第一次运行时,对他加载过的类的数据进行收集并归档,记录到数据文件中。之后,这些数据文件还可以被后续的JVM进程使用。相比于每次从class文件中加载类信息,AppCDS可以节省JVM初始化过程中的时间和资源。
# 将类信息归档到hello.jsa文件中。
java -Xshare:dump -XX:SharedArchiveFile=hello.jsa -version
# 使用归档文件启动,并打印类加载日志
java -XX:SharedArchiveFile=hello.jsa -Xlog:class+load -version
# 有hello.jsa文件,加载的最后一个类
[0.021s][info][class,load] java.nio.charset.CoderResult source: shared objects file
# 删掉hello.jsa文件后,依然可以加载类,但是比有归档文件时会慢一些
[0.038s][info][class,load] java.nio.charset.CoderResult source: jrt:/java.base
在部署微服务应用时这或许可以提高启动速度
定制JVM的GC参数
RocketMQ的案例也能看到,在优化整体布局时, 在JDK8以前版本时,还设定了-Xmn参数,但是在JDK8以后的版本中,就没有设置这个参数,这其实也跟GC算法有关。
- -Xmn :
设置分代收集的GC中年轻代的最大大小。堆中的年轻代用于存放新new出来的对象。年轻代的GC会比其他区域更频繁。年轻代太小,就会导致过于频繁的youngGC,而如果年轻代过大,又会导致youngGC的回收效果变差,加大fullGC的压力,从而进一步影响整个程序的运行效率。官方明确建议,对于基础的分代收集器,建议保持年轻代的大小在整个堆内存的25%到50%之间。而对于G1垃圾回收器,由于G1不再有严格固定的年轻代,所有官方明确建议,对G1垃圾回收器,不要设置-Xmn参数。
G1
G1 GC 是一种分代的、并发的、基于区域的垃圾回收器,它将堆内存划分为多个独立的区域(Regions),每个区域可以是 Eden 区、Survivor 区或者 Old 区。为了保持系统的响应性,G1 GC 会尽量在达到用户设定的停顿时间目标(通过 -XX:MaxGCPauseMillis 参数指定)前进行垃圾回收。
他的各个Region的划分并不固定
-
-XX:+UseG1GC :
使用G1垃圾回收器。在JDK17中,是默认选项。G1垃圾回收器适合那些需要大量堆内存(建议是6GB以上)同时还需要有稳定的GC延迟(STW延迟时间稳定在0.5秒以下)的应用。 -
-XX:G1HeapRegionSize=size
设定每个Region的大小。这个值必须是2的N次幂,且范围在1MB到32MB之间。
这个参数的默认值是不固定的,通常JVM会将堆内存划分为2048个Region。每个Region的大小就是 堆内存/2048。 -
-XX:G1ReservePercent=percent
G1为分配担保预留的空间比例。在突发的内存分配需求活垃圾回收效率下降时,G1任然有足够的缓冲空间来避免长时间的停顿。
这个参数的默认值是10%,这意味着堆内存的10%将被保留为空闲状态。也就是老年代会预留10%的空间来给新生代的对象晋升,如果经常发生新生代晋升失败而导致Full GC,那么可以适当调高此阈值。但是调高此值同时也意味着降低了老年代的实际可用空间。 -
-XX:InitiatingHeapOccupancyPercent=percent
触发全局并发标记的老年代使用占比。这个参数的默认值是45,表示当整个堆中,老年代Region达到45%时,G1就会开始并发标记周期。RocketMQ将这个参数设定为30,显然是为了更积极的进行GC。
但是,并不意味着每次堆内存用到了30%就开始GC,与此相关的还有另外两个参数:-XX:G1UseAdaptiveIHOP 和 -XX:G1AdaptiveIHOPNumInitialSamples。
其中,-XX:G1UseAdaptiveIHOP 是一个bool型的参数,默认是启动的。这个参数启动后,G1只会在 -XX:G1AdaptiveIHOPNumInitialSamples参数指定的前面有几次GC活动中按照-XX:InitiatingHeapOccupancyPercent参数进行计算。之后,G1会自动根据目标调整参数。-XX:G1AdaptiveIHOPNumInitialSamples 的默认值是3。 -
-XX:SoftRefLRUPolicyMSPerMB=time
他表示在每MB的堆内存中,软引用经过多长时间才被认为过期。默认值是1000,表示1秒。
相比JDK8,JDK17中新增了几个G1相关的参数
-
-XX:ParallelGCThreads=threads
设置GC的工作线程数。默认值取决于GC算法以及CPU核心数。比如对于G1,可以通过以下方式设置线程数为2: -XX:ParallelGCThreads=2 -
-XX:G1HeapWastePercent=percent
触发Mixed GC的堆垃圾占比。也就是在全局标记结束后能够统计出可被回收的垃圾占整堆的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC,如果不超过,那么会在之后的某次Young GC中重新执行全局并发标记。可以尝试适当的调高此阈值,能够适当的降低Mixed GC的频率。 -
-XX:G1OldCSetRegionThresholdPercent=percent
设置一次混合GC中需要清理的Old区的内存比例。默认值是堆空间的10%。这也是G1非常重要的一个参数。将他调大,可以降低G1的频率,但是会让每一次GC的时间变长。 -
-XX:G1MixedGCCountTarget=number
设置G1垃圾回收器的线程上限。HotSpot会为了达到清理G1OldCSetRegionThresholdPercent比例的Old区的目标,会自动计算需要启动几个G1垃圾回收器。但是垃圾回收器的个数不会超过这个上限。默认值是8。
ZGC重要参数
- -XX:+UseZGC
Enables the use of the Z garbage collector (ZGC). This is a low latency garbage collector, providing max pause times of a few milliseconds, at some throughput cost. Pause times are independent of what heap size is used. Supports heap sizes from 8MB to 16TB. - -XX:ZAllocationSpikeTolerance=factor
Sets the allocation spike tolerance for ZGC. By default, this option is set to 2.0. This factor describes the level of allocation spikes to expect. For example, using a factor of 3.0 means the current allocation rate can be expected to triple at any time. - -XX:ZCollectionInterval=seconds
Sets the maximum interval (in seconds) between two GC cycles when using ZGC. By default, this option is set to 0 (disabled). - -XX:ZFragmentationLimit=percent
Sets the maximum acceptable heap fragmentation (in percent) for ZGC. By default, this option is set to 25. Using a lower value will cause the heap to be compacted more aggressively, to reclaim more memory at the cost of using more CPU time. - -XX:+ZProactive
Enables proactive GC cycles when using ZGC. By default, this option is enabled. ZGC will start a proactive GC cycle if doing so is expected to have minimal impact on the running application. This is useful if the application is mostly idle or allocates very few objects, but you still want to keep the heap size down and allow reference processing to happen even when there are a lot of free space on the heap. - -XX:+ZUncommit
Enables uncommitting of unused heap memory when using ZGC. By default, this option is enabled. Uncommitting unused heap memory will lower the memory footprint of the JVM, and make that memory available for other processes to use. - -XX:ZUncommitDelay=seconds
Sets the amount of time (in seconds) that heap memory must have been unused before being uncommitted. By default, this option is set to 300 (5 minutes). Committing and uncommitting memory are relatively expensive operations. Using a lower value will cause heap memory to be uncommitted earlier, at the risk of soon having to commit it again.
GC日志处理
DK8与JDK17在日志方面的参数差异
RocketMQ中对JDK8以后的版本,统一采用以下参数打印GC日志
-Xlog:gc*:file=${GC_LOG_DIR}/rmq_srv_gc_%p_%t.log:time,tags:filecount=5,filesize=30M
-
第一部分是文件名
-
第二部分表示历史文件的后缀,有以下几个选项:
time (t), utctime (utc), uptime (u), timemillis (tm), uptimemillis (um), timenanos (tn), uptimenanos (un), hostname (hn), pid (p), tid (ti), level (l), tags (tg)
- 第三部分表示历史文件的个数和大小。RocketMQ中的配置就表示保留5个文件,每个文件写满30M就切换下一个文件。
远程调试
#JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n"
IDEA中配置RemoteDebug,即可用本地机器模拟的远端服务器。这种远程调试的方式显然是不能用在生产环境的。因为打开远程调试后,服务端的应用程序必须监听到调试请求才会正常执行。如果你把IDEA中的调试任务终止了,远端的应用程序就会重新回归到阻塞状态。