版权声明:本文为博主原创文章,未经博主允许不得转载https://blog.csdn.net/qq_37146354/article/details/97244536
统一生产JVM参数
背景:公司要重构系统,不想使用官方的JVM配置,想找一些比较合适的JVM配置参数,所以我利用业余时间收集了JVM的参数,每个参数都有明确的说明,以便配置人员可以从中挑选。下文以内存选项、垃圾回收选项、故障排除选项、日志选项和优化选项进行统计,内容不全,仅仅供参考。
1 Memory Options (内存选项)
1.1 -Xms -Xmx -XX:NewRatio=n
最大堆内存-Xmx
最小堆内存-Xms:此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
**-XX:NewRatio=n:**设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
1.2 永久代(PermSize) 和 元空间(MetaspaceSize)
JDK<1.8
-XX:PermSize=128m -XX:MaxPermSize=512m
说明:
永久代:JVM的方法区,也被称为永久代。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
-XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)。
-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。
JDK>=1.8
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
说明:
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小。
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
为什么要移除永久代?
PermGen很难调整,PermGen中类的元数据信息在每次FullGC的时候可能被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class总数,常量池的大小,方法的大小等。
Metaspace特性:
- 内存分配模型
大部分类元数据都在本地内存中分配。
- 容量
默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。
- 垃圾回收
对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。适时地监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。
- 内存的影响
Java堆 空间有所增长。
1.3 启动时预申请内存
-XX:+AlwaysPreTouch
说明:
JAVA进程启动的时候,虽然我们可以为JVM指定合适的内存大小,但是这些内存操作系统并没有真正的分配给JVM,而是等JVM访问这些内存的时候,才真正分配,这样会造成以下问题:
-
GC的时候,新生代的对象要晋升到老年代的时候,需要内存,这个时候操作系统才真正分配内存,这样就会加大young gc的停顿时间;
-
可能存在内存碎片的问题。
设置此参数后,JVM就会先访问所有分配给它的内存,让操作系统把内存真正的分配给JVM,后续JVM就可以顺畅的访问内存了
1.4 线程栈内存(根据实际情况)
-Xss256k
说明:如果线程数较多,函数的递归较少,线程栈内存可以调小节约内存,默认1M。
1.5 堆外内存(根据实际情况)
-XX:MaxDirectMemorySize=2g
说明:堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
优点:
-
减少了垃圾回收。
-
加快了复制的速度:堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。
缺点:
堆外内存的缺点就是内存难以控制,使用了堆外内存就间接失去了JVM管理内存的可行性,改由自己来管理,当发生内存溢出时排查起来非常困难。
使用场景:
-
多用于网络编程中,实现zero copy,数据不需要再native memory和jvm memory中来回copy
-
由于构造和析构Direct Buffer时间成本高,建议使用缓冲池,参见netty的实现
1.6 Code Cache
-XX:ReservedCodeCacheSize=240M
-XX:+PrintCodeCache
说明:
JIT及时编辑:后端编译器,它在程序运行期间将字节码转变成机器码。
Code Cache:编译后的代码被缓存了起来。缓存编译后的机器码的内存区域就是codeCache。除了JIT编译的代码之外,Java所使用的本地方法代码(JNI)也会存在codeCache中。
不同版本的JVM**、不同的启动方式codeCache****的默认大小也不同:**
(图片来自网络)
2 GC Options (垃圾回收选项)
2.1 CMS算法:
-XX:+UseConcMarkSweepGC
设置年老代为并发收集。
2.2 -XX:CMSInitiatingOccupancyFraction=n
如果n=75,第一次CMS垃圾收集会在老年代被占用75%时被触发。
2.3 -XX:+UseCMSInitiatingOccupancyOnly
当该标志被开启时,JVM通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不仅仅是第一次。
2.4 -XX:+ExplicitGCInvokesConcurrent
命令JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC。
2.5 -XX:+ParallelRefProcEnabled (CMSGC并发执行)
CMSCG并行处理应用参数:
Terminator object是用来同步和管理gc线程的对象. 它会记录到目前为止已经完成的线程数_complete_threads , 当一个gc线程干完活后,他会把数_complete_threads+1,当terminator object确定已经完成的线程数_complete_threads==预先设置的所有的gc线程数_n_thread,所有的gc线程就会退出,否则其他的gc线程就会等待。_n_thread在构造时为0, 后面一直没有被重设过,当GC并发开始到处理完后, 一直等待GC Terminator通知它结束, 所以它一直处于自旋锁的状态, 使得CPU使用率很高。此BUG在JDK7中已经解决,JDK6u26和JDK6u30出现过这个BUG。
建议:
在JDK7以后的版本,CMS里remark时间过长时候或者回收弱引用时间过长时开启CMSGC并发执行 -XX:+ParallelRefProcEnabled
在JDK7以前的版本关闭CMSGC并发执行 -XX:-ParallelRefProcEnabled
2.6 -XX:+CMSParallelInitialMarkEnabled
初始标记代码实现并行化
2.7 -XX:MaxTenuringThreshold=n
用于控制对象能经历多少次Minor GC才晋升到老年代
2.8 -XX:+UnlockDiagnosticVMOptions
在JVM启动后,在命令行中可以输出所有XX参数和值,UnlockDiagnosticVMOptions解锁任何额外的隐藏参数,命令行中可以看出参数被用户或者JVM赋值。
2.9 -XX:ParGCCardsPerStrideChunk=n
Mirnor GC耗时两个因素决定:
-
复制活跃对象
-
扫描card table(老年代对象引用新生代对象)的时间
Java虚拟机用了一个叫做CardTable(卡表)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小。这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域,提高效率减少MinorGC的停顿时间。JVM通过ParGCCardsPerStrideChunk参数设置每个线程每次扫描的Card数量,默认是256,相当于是把老年代分成许多strides,每个线程每次扫描一个stride,每个stride大小为512*256 = 128K。多线程在扫描这么多的strides时就涉及到调度和分配的问题,stride数量太多就会导致线程在stride之间切换的开销增加,进而导致GC暂停时间增长。这个值不能设置的太大,因为GC线程需要扫描这个stride中老年代对象持有的新生代对象的引用,如果只有少量引用新生代的对象那就导致浪费了很多时间在根本不需要扫描的对象上。
2.10 -XX:ParallelGCThreads=n (根据实际情况)
表示JVM在进行并行GC的时候,用于GC的线程数。
ParallelGCThreads值的具体算法:
-
如果用户显示指定了ParallelGCThreads,则使用用户指定的值
-
如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8,是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
-
如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。
2.11 -XX:ConcGCThreads=n (根据实际情况)
设置并行标记的线程数。将参数设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
2.12 -XX:+CMSScavengeBeforeRemark (根据实际情况)
CMSGC会以新生代作为gc root,在remark之前做一次Ygc可以回收大部分的对象,从而减少gc root扫描的开销。如果remark不是什么性能瓶颈,不进行此配置,毕竟多一次Ygc也会耗时间。
2.13 -XX:-CMSClassUnloadingEnabled (根据实际情况)
如果启用CMSClassUnloadingEnabled,GC将扫描PermGen,并删除不再使用的类。如果永久代使用不会增长,关闭CMS时ClassUnloading,降低CMS GC时出现缓慢的几率。
3 GC log Options, only for JDK7/JDK8(日志选项)
3.1 -XX:+PrintGCDetails
输出GC的详细日志。
3.2 -XX:+PrintGCDateStamps
输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)。
3.3 -XX:+PrintPromotionFailure
是多大的新生代对象晋升到老生代失败从而引发Full GC时的情况。
3.4 -XX:+PrintGCApplicationStoppedTime
显示应用程序在安全点停止的时间,而不止是GC暂停的。大多数情况下,安全点是由垃圾收集的世界各个阶段引起的。
3.5 -XX:+PrintGCCause (JDK8默认打开)
打印GC原因。
3.6 -XX:+PrintHeapAtGC
打印GC前后的各代大小。
3.7 -XX:+PrintTenuringDistribution
打印存活区每段年龄的大小。通过打开 PrintTenuringDistribution 获取更多的 GC 信息来优化对象从新生代到老生代的提升率, 以及优化 Minor GC 的响应时间。
3.8 -XX:+UnlockDiagnosticVMOptions -XX:PrintFLSStatistics=n
因为CMS GC会有碎片问题,而随着碎片的越来越严重,GC性能会变差直到发生FullGC,而FullGC时STW通过会超过数秒,这对OLTP系统来说是致命的,通过这个参数可以在gc日志中输出free list方式分配内存后内存统计情况和碎片情况。
3.9 -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=n -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm-${APPID}.log
打印安全点日志,找出GC日志里非GC的停顿的原因
4 Optimization Options (优化选项)
4.1 -XX:-UseBiasedLocking
如果程序没有竞争,则取消之前已经取得锁的线程的同步操作。某一个锁被一个线程获取之后,便进入了偏向锁模式,当该线程再次请求这个锁时,就无需再进行相关的同步操作,从而节省了操作时间。在竞争激烈的场景下可以使用-XX:-UseBiasedLocking参数禁用偏向锁。
4.2 -XX:AutoBoxCacheMax=n
JAVA进程启动的时候,会加载rt.jar这个核心包的,rt.jar包里的Integer自然也是被加载到JVM中,Integer里面有一个IntegerCache缓存。IntegerCache有一个静态代码块,JVM在加载Integer这个类时,会优先加载静态的代码。当JVM进程启动完毕后, -128 ~ +127 范围的数字会被缓存起来,调用valueOf方法的时候,如果是这个范围内的数字,则直接从缓存取出。 超过这个范围的,就只能构造新的Integer对象了。
4.3 -XX:+PerfDisableSharedMem
每次进入安全点(比如GC), JVM都会默默的在/tmp/hperf 目录写上一点statistics数据,如果刚好遇到PageCache刷盘,把文件阻塞了,就不能结束这个Stop the World的安全点了。用此参数可以禁止JVM写statistics数据,代价是VisualVM和jstat用不了,只能用JMX取数据,但在生产环境本来就不需要VisaulVM。
4.4 -XX:-TieredCompilation (JDK1.8以上)
(图片来自网络)
-XX:-TieredCompilation 禁用中间编译层(1,2,3),以便在最大优化级别(C2)解释或编译方法。
优点缺点:
(图片来自网络)
当关闭分层编译后,JVM启发式地根据您的CPU决定应用哪种模式; 如果您有多个处理器或64位VM,那么它将使用服务器VM(C2),否则它将使用客户端VM(C1)。
4.5 -XX:-UseCounterDecay
方法调用计数器client默认1500次,server默认10000次,可以通过参数-XX:CompileThreshold来设定。调用方法时,会先判断是否存在编译过的版本,如果有则调用该版本,否则计数器加1,然后看方法调用计数器和回边计数器之和是否超过方法调用计数器的阈值。超过,则提交编译请求。方法调用计数器并不是统计方法调用绝对次数,而是一个相对执行频率,超过一定时间,如果方法调用次数不足以让它提交给编译器,则计数器就会被减少一半,这种现象称为热度衰减(Counter Decay),进行热度衰减的动作是在垃圾回收时顺便进行的,而这段时间就被称为半衰周期(Counter Half Life Time)可用-XX:-UseCounterDecay来关闭热度衰减,用-XX:CounterHalfLifeTime来设置半衰时间。
5 Trouble shooting Options (故障排除选项)
5.1 -XX:+PrintCommandLineFlags
JVM 打印出那些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值
5.2 -XX:-OmitStackTraceInFastThrow
JDK为了性能会做一个优化,即JIT重新编译后会抛出没有堆栈的异常,因此在频繁抛出某个异常一段时间后,该优化开始起作用,即只抛出没有堆栈的异常信息。为了查询问题方便,强制要求JVM始终抛出含堆栈的异常。
5.3 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/
OOM 时进行HeapDump,导出内存溢出的堆信息,但此时会产生较高的连续IO,如果是容器环境,有可能会影响他的容器。
5.4 -XX:+DebugNonSafepoints
让jvm记录非安全点。
Java应用性能分析工具:
async-profiler:(需添加这选项)
来采集cpu profile数据,并且配合火焰图生成工具工具FlameGraph来生成cpu火焰图,并且从火焰图中找到热点代码。如果JAVA代理在JVM启动时加载(通过使用-agentpath选项),则不需要该选项,但如果在运行时附加代理,则需要(但不是必需)
Honest Profiler:(无需添加这选项)
它准确地描述了应用程序,避免了对具有安全点的地方的固有偏见。
它描述的应用程序的开销明显低于传统的分析技术,使其适用于生产。
5.5 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder (用在生产环境要许可)
在JDK 8u40之前,如果要使用Java Flight Recorder(JFR)并为特定Java应用程序创建飞行记录,则必须同时指定启动应用程序-XX:+UnlockCommercialFeatures和-XX:+FlightRecorder启动应用程序。在JDK 8u40中,您不必在命令行中指定这些选项。JFR提供了多种方法来解锁商业功能并在应用程序运行时启用JFR。这些包括Java Mission Control中的java命令行选项,jcmd诊断命令和图形用户界面(GUI)控件。
JFR 记录了关于 Java 运行时及运行在其内的 Java 应用程序的详细信息,记录用少量的开销完成。数据是作为时间上的数据点(称为事件)记录的。典型的事件可以是线程等待锁、GC、CPU 周期使用数据等。
6 JMX Options (JMX 选项)
JMX(Java Management Extensions,即Java管理扩展)是Java平台上为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。