线上使用RocketMQ有段时间了,可以说是相当稳定,除了代码和架构方面合理意外,其一系列的启动优化参数也是非常值得研究,接下来以broker的启动参数为例进行一次浅析。
/usr/lib/jvm/java-1.8.0-openjdk.x86_64/bin/java -server -Xms4g -Xmx4g -Xmn2g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:MaxDirectMemorySize=15g -XX:-UseLargePages -XX:-UseBiasedLocking -Djava.ext.dirs=/usr/lib/jvm/java-1.8.0-openjdk.x86_64/jre/lib/ext:/home/user/rocketmq-all-4.2.0-master/bin/../lib -cp .:/home/user/rocketmq-all-4.2.0-master-b/bin/../conf:.:/usr/lib/jvm/java-1.8.0-openjdk.x86_64/lib:/usr/lib/jvm/java-1.8.0-openjdk.x86_64/jre/lib: org.apache.rocketmq.broker.BrokerStartup -c /home/user/rocketmq-all-4.2.0-master/conf/2m-noslave/broker-b.properties
可以看出,一条长长的命令,其中的JVM参数真实不少。
-server
#指定以server方式启动,这个是x64下面的必选项了,一般默认也是server模式,保险起见显示加上,其性能要比client模式好太多了。
-Xms4g
-Xmx4g
#初始堆大小和最大对大小都是4g,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g
#设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小(或MetaSpace空间大小)。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-XX:+UseG1GC
#开启G1垃圾收集器
-XX:G1HeapRegionSize=16M
#指定堆中一个region分配的大小。每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。堆内存中一个Region的大小可以通过 -XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实践大小。G1 region 跨越1M-32M(2的指数),分配要求稍微超过4M。因此,8M的region的尺寸不足以避免humongous分配。需要16MB。
-XX:G1ReservePercent=25
#预留内存占堆总大小的百分比,防止晋升失败的情况,默认值是10%。一个JVM当在回收存活或者晋升对象的时候,栈区域溢出了就会发生失败,因为堆的使用已经到达了最大值,不能再扩展。G1 GC设置上限预留内存,以防万一"to-space"Survivor区域需要更多的空间。
-XX:InitiatingHeapOccupancyPercent=30
#整个堆使用达到百分之多少的时候,启动GC周期. 基于整个堆,不仅仅是其中的某个代的占用情况,G1根据这个值来判断是否要触发GC周期, 0表示一直都在GC,默认值是45(占用了45%)。此处调节为30%,尽早触发GC周期。0表示一直在GC。
-XX:SoftRefLRUPolicyMSPerMB=0
#这个参数比较有用的,官方解释是:Soft reference在虚拟机中比在客户集中存活的更长一些。其清除频率可以用命令行参数 -XX:SoftRefLRUPolicyMSPerMB=
-XX:SurvivorRatio=8
#设置eden区域大小与其中一个survivor的比值是8。suvivor区有2个,即总的survivor和eden的比值是2:8。
-verbose:gc #或者 -XX:+PrintGC两者作用相同。在虚拟机发生内存回收时在输出设备显示信息,格式如下:
[Full GC268K->168K(1984K), 0.0187390 secs]
-Xloggc:/dev/shm/mq_gc_%p.log #gc log的打印路径和格式
-XX:+PrintGCDetails #相比 -XX:+PrintGC这个会打印更详细的gc日志信息
-XX:+PrintGCDateStamps or XX:+PrintGCTimeStamps #GC发生时间戳,两种不同格式
-XX:+PrintGCApplicationStoppedTime #打印gc一共停顿了多长时间
#通过上面5个参数的设定可以在指定路径下获得详细的gc运行的日志信息
-XX:+PrintAdaptiveSizePolicy
#打印自适应收集的大小,默认关闭。配合-XX:-UseAdaptiveSizePolicy 来实现关闭自适应大小以及获取在垃圾回收器日志里面额外的survivor空间统计信息。
-XX:+UseGCLogFileRotation #默认关闭,即不进行切分。开启日志切分。
-XX:NumberOfGCLogFiles=5 #默认为0,即不做限制。开启时控制log数量是5
-XX:GCLogFileSize=30m #默认为0,即不做任何限制。开启时控制为30m
通过同时设定上面三项可以起到日志循环滚动作用,可以防止耗尽服务器的磁盘。
-XX:+AlwaysPreTouch
#启用main之前JVM就会先访问所有分配给它的内存,让操作系统把内存真正的分配给JVM.后续JVM就可以顺畅的访问内存了。
JAVA进程启动的时候,虽然我们可以为JVM指定合适的内存大小,但是这些内存操作系统并没有真正的分配给JVM,而是等JVM访问这些内存的时候,才真正分配,这样会造成以下问题。
1、GC的时候,新生代的对象要晋升到老年代的时候,需要内存,这个时候操作系统才真正分配内存,这样就会加大young gc的停顿时间; 2、可能存在内存碎片的问题。
-XX:MaxDirectMemorySize=15g
#当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC。该值是有上限的,默认是64M,最大为sun.misc.VM.maxDirectMemory(),在程序中中可以获得-XX:MaxDirectMemorySize的设置的值。通过调大来减少直接堆触发的Full GC。
direct ByteBuffer通过full gc来回收内存的,direct ByteBuffer会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC那么就无法回收该快内存了,-XX:+DisableExplicitGC标志自动将System.gc()调用转换成一个空操作,就是应用中调用System.gc()会变成一个空操作。那么如果设置了就需要我们手动来回收内存了。
除了FULL GC还有别的能回收direct ByteBuffer吗?CMS GC会回收Direct ByteBuffer的内存,CMS主要是针对old space空间的垃圾回收。但是是Oracle JDK 6u32以后的版本。
ByteBuffer有两种:
heap ByteBuffer -> -XX:Xmx
1.一种是heap ByteBuffer,该类对象分配在JVM的堆内存里面,直接由Java虚拟机负责垃圾回收,
direct ByteBuffer -> -XX:MaxDirectMemorySize
2.一种是direct ByteBuffer是通过jni在虚拟机外内存中分配的。通过jmap无法查看该快内存的使用情况。只能通过top来看它的内存使用情况。
-XX:-OmitStackTraceInFastThrow
#禁用fast throw优化,加速线上问题排查。这是HotSpot VM专门针对异常做的一个优化,,称为fast throw,当一些异常在代码里某个特定位置被抛出很多次的话,HotSpot Server Compiler(C2)会用fast throw来优化这个抛出异常的地方,直接抛出一个事先分配好的、类型匹配的对象,这个对象的message和stack trace都被清空。优点:抛出这个异常非常快,不用额外分配内存,也不用爬栈。缺点:正好是需要知道哪里出问题的时候看不到stack trace了,不利于排查问题
-XX:-UseLargePages
#禁用大页(huge pages)优化。某些情况下会导致内存浪费或实例无法启动。默认启动。
-XX:-UseBiasedLocking
#JVM默认启用偏向锁。在竞争激烈的场合,偏向锁会增加系统负担(每次都要加一次是否偏向的判断) 。
在使用G1时,尽量不要配置 -XX:NewSize=4g -XX:MaxNewSize=5g展示用户限制region的范围在4-5G之间,因此限制了G1 GC的适应能力。如果G1需要去掉限制设置更小的值,它做不到;如果G1 GC需要增加空间范围,超过了给它分配的空间,它做不到!
-Xss15120 #每增加一个线程(thread)就会立即消耗15M内存,而最佳值应该是128K,默认值好像是512k。
-XX:NewRatio=4 #表示新生代(eden + 2*survivor) 和老年代(不包括永久带或metaspace)的比值
-XX:+PrintGCApplicationConcurrentTime #GC之间运行了多少时间