目录
jvm crash & heap dump & core dump & High CPU
模拟jvm crash使用CrashAnalysis帮助分析crash 文件,简单例子:
模拟OOM使用eclipse MAT分析heap dump文件,简单例子:
3) jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]
JVM性能调优是一个长期的循序渐进的过程,是一个有深度且有广度的课题。不同的应用场景适用于不同的优化方案,本文只通过内存优化、GC垃圾收集器优化、线程调优、JIT编译器调优等四个方向提供一些相关的优化方法。并且会持续的丰富下去。如有错误的地方,非常希望能够一起探讨、学习、进步。本文只针对JDK8及后续版本,默认运行在server模式下。本文仅限于Oracle hotspot的jvm层面调优和生产环境的问题排查,并不会涉及到代码层面编写的各种工作。
注:阅读本文前需要对JMM、OOM、GC收集器以及GC收集算法、多线程与锁或无锁CAS(这里的线程都是用户线程,但可以帮助区分和理解本文中所说的工作线程也就是JVM工作线程)、JIT编译器的运作方式有一定的了解。
JMM参考资料:
https://blog.csdn.net/javazejian/article/details/72772461
OOM参考资料:
https://blog.csdn.net/sunquan291/article/details/79109197
GC收集器参考资料:
https://blog.csdn.net/jl19861101/article/details/88169405
https://blog.csdn.net/jl19861101/article/details/88566316
多线程参考资料:
https://blog.csdn.net/jl19861101/article/details/87357979
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/javazejian/article/details/72772470
https://blog.csdn.net/javazejian/article/details/75043422
JIT参考资料:
https://blog.csdn.net/jl19861101/article/details/88667402
https://blog.csdn.net/u013678930/article/details/52042266
----------------------------------------------------------------------------------
或参考《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》《实战Java虚拟机:JVM故障诊断与性能优化》
内存调优
内存调优分为堆内存调优与非堆内存调优
jvm堆内存分配示意图:
对象内存分配流程:
通过命令jhsdb jmap --heap --pid <pid>或者jmap -heap <pid>查看heap情况
一、通用优化
- -XX:-RestrictContended,开关参数。默认开启。关闭此参数可以消除伪共享参数,但需要配合@sun.misc.Contended注解才会生效。
- -XX:+UseCompressedOops,开关参数。默认开启。该参数指定是否启用指针压缩优化方式。指针压缩优化方式可以减小内存占用。
- -XX:-UseLargePages,开关参数。默认关闭。该参数指定是否支持大内存页优化。
测试linux是否支持largepage:
# cat /proc/meminfo | grep Huge
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB
如果有HugePage字样的输出内容,说明OS是支持大内存分页的。Hugepagesize就是默认的大内存页size。接下来,为了让JVM可以调整大内存页size,需要设置下OS 共享内存段最大值和大内存页数量。
共享内存段最大值,建议这个值大于Java Heap size,这个例子里设置了4G内存。
$ echo 4294967295 > /proc/sys/kernel/shmmax
大内存页数量,这个值一般是 Java进程占用最大内存/单个页的大小,比如java设置1.5G,单个页10M,那么数量为1536/10 = 154。
$ echo 154 > /proc/sys/vm/nr_hugepages
- -XX:LargePageSizeInBytes,数值参数。该参数指定单个页大小。
二、堆内存调优涉及参数
- -Xmx:参数设置JVM最大可用堆(单位k,m,g)。表示java虚拟机堆区内存可被分配的最大上限,通常为操作系统可用内存的1/4大小。但是开发过程中,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。
- -Xms:参数设置JVM初始堆大小(单位k,m,g)。表示java虚拟机堆区内存初始内存分配的大小,通常为操作系统可用内存的1/64大小即可,但仍需按照实际情况进行分配。有可能真的按照这样的一个规则分配时,设计出的软件还没有能够运行得起来就挂了。
- -Xmn:新生代大小(单位k,m,g)。可以通过这个值得到老年代大小:-Xmx减去-Xmn。该参数设置以后-XX:NewSize、-XX:MaxNewSize:都会等于该值。
- -XX:NewSize:初始新生代大小。
- -XX:MaxNewSize:最大新生代大小。
- -XX:NewRatio:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。等同于设置-XX:MaxNewSize。
- -XX:SurvivorRatio:Eden/Survivor的值. 这个值的说明, 很多网上转载的都是错的. 8表示Survivor:Eden=1:8, 因为survivor区有2个, 所以Eden的占比为8/10. Ratio of eden/survivor space size. -XX:SurvivorRatio=6 sets the ratio between each survivor space and eden to be 1:6, each survivor space will be one eighth of the young generation.
- -XX:MaxHeapFreeRatio:GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比),则收缩堆内存的预估最大值, 预估堆内存是堆大小动态调控的重要选项之一. 堆内存预估最大值一定小于或等于固定最大值(-Xmx指定的数值). 前者会根据使用情况动态调大或缩小, 以提高GC回收的效率
- -XX:MinHeapFreeRatio:GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比), 则放大堆内存的预估最大值
- -XX:MaxTenuringThreshold:默认值15。用来设置多少次Minor GC后对象会进入Old区域。晋升年龄最大阈值,默认15。在新生代中对象存活次数(经过YGC的次数)后仍然存活,就会晋升到老年代。每经过一次YGC,年龄加1,当survivor区的对象年龄达到TenuringThreshold时,表示该对象是长存活对象,就会直接晋升到老年代。
- -XX:TargetSurvivorRatio:设定survivor区的目标使用率。默认50,即survivor区对象目标使用率为50%。
- -XX:+UseTLAB:开关参数。启动TLAB。默认启动。
- -XX:TLABWasteTargetPercent:设置TLAB空间所占用Eden空间的百分比大小。
- -XX:TLABRefillWasteFraction:设置维护进入TLAB空间单个对象大小,比例值,默认1/64,对象大于该值会去堆创建。
- -XX:+ResizeTLAB:开关参数,默认开启。默认情况下TLAB和RefillWaste大小会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
- -XX:TLABSize:手动指定TLAB大小。默认0。
- -XX:MinTLABSize:最小TLAB大小。默认2048。
- -XX:-AlwaysPreTouch:开关参数。默认关闭。启动时就把参数里说好了的内存全部舔一遍,可能令得启动时慢上一点,但后面访问时会更流畅,比如页面会连续分配,比如不会在晋升新生代到老生代时才去访问页面使得GC停顿时间加长。不过这选项对大堆才会更有感觉一点。(参考资料:https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector-tuning.htm#JSGCT-GUID-4914A8D4-DE41-4250-B68E-816B58D4E278)
- -XX:YoungGenerationSizeIncrement数值参数,默认20。虚拟机动态调整年轻代增加比率。默认20%。
- -XX:TenuredGenerationSizeIncrement数值参数,默认20。虚拟机动态调整老年代的增加比率。默认20%。
- -XX:AdaptiveSizeDecrementScaleFactor数值参数,默认4。虚拟机动态调整空间的收缩比率。如果增长比率是20%。那么收缩比率=20/4=5%。那么空间收缩比率为5%。
注意:JVM会将每个对象的年龄信息、各个年龄段对象的总大小记录在“age table”表中。基于“age table”、survivor区大小、survivor区目标使用率(-XX:TargetSurvivorRatio)、晋升年龄阈值(-XX:MaxTenuringThreshold),JVM会动态的计算tenuring threshold的值。一旦对象年龄达到了tenuring threshold就会晋升到老年代。
为什么要动态的计算tenuring threshold的值呢?假设有很多年龄还未达到TenuringThreshold的对象依旧停留在survivor区,这样不利于新对象从eden晋升到survivor。因此设置survivor区的目标使用率,当使用率达到时重新调整TenuringThreshold值,让对象尽早的去old区。
内容来源:https://blog.csdn.net/zero__007/article/details/52797684可以参考:https://blog.csdn.net/foolishandstupid/article/details/77596050
三、非堆内存调优涉及参数
- -Xss:设置每个用户线程的堆栈大小。
- -XX:+EliminateAllocations 启用标量替换,允许对象打散分配到栈上。此参数需要配合DoEscapeAnalysis参数使用。
- -XX:AutoBoxCacheMax=128。默认值128。Integer i = 3;这语句有着 int自动装箱成Integer的过程,JDK默认只缓存 -128 ~ +127的int 和 long,超出范围的数字就要即时构建新的Integer对象。
- -XX:MetaspaceSize:分配给类元数据空间的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark ). 此值为估计值. MetaspaceSize设置得过大会延长垃圾回收时间. 垃圾回收过后, 引起下一次垃圾回收的类元数据空间的大小可能会变大
- -XX:MaxMetaspaceSize:是分配给类元数据空间的最大值, 超过此值就会触发Full GC. 此值仅受限于系统内存的大小, JVM会动态地改变此值
- -XX:MinMetaspaceFreeRatio:默认值40。表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小比例,不够就会导致垃圾回收。
- -XX:MaxMetaspaceFreeRatio:默认值70。表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最大比例,不够就会导致垃圾回收。
- -XX:InitialCodeCacheSize:初始JIT编译器本地机器码缓存区大小。初始化大小不应该小于系统最小内存页大小。
- -XX:ReservedCodeCacheSize:(单位k,m,g)默认240M,如果使用-XX:-TieredCompilation参数禁用分层编译,则默认大小为48M。本地机器码缓冲区大小。JIT编译器编译后的机器码保存在内存中,他的大小由此参数控制。一旦代码缓存耗尽,JIT就会停止,并且在整个虚拟机的声明周期中,不会再启动了。这个参数最大可设置2GB,超过此限制将会产生错误(Invalid ReservedCodeCacheSize=2049M. Must be at most 2048M.Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.)。
- -XX:SegmentedCodeCache:开关参数,默认开启。JDK9提供的本地代码缓冲区分段缓存功能。和原来的单一缓存区域不同的是, 新的代码缓存根据代码自身的生命周期而分为三种:永驻代码(non-method code),短期代码(profiled code),长期代码(Non-profiled code)。缓存分段会在各个方面提升程序的性能, 比如做垃圾回收扫描的时候可以直接跳过非方法代码(永驻代码), 从而提升效率。
- -XX:MaxDirectMemorySize:直接内存最大值。即NIO进行操作时,可以分配的最大缓存值,默认和heap最大值一致。
- -XX:CompressedClassSpaceSize:类指针压缩空间大小, 默认为1G
注意:以下结论在JDK11.0.2_LTS版本中测试所得。其余版本并没有测试过。所以不同的版本也有可能会有不同的结论。
1)参数设置存在先后顺序。以-Xmn、-XX:NewSize、-XX:MaxNewSize为例。
-Xmn等同于-XX:NewSize、-XX:MaxNewSize的设置。如果设置了-Xmn的值,NewSize与MaxNewSize的值都会等于该值。但是如果三个参数同时设置了就会导致少许的混乱。jvm会按照参数编写的先后顺序设置系统值。例如参数编写顺序为:-XX:MaxNewSize=256m -Xmn512m -XX:NewSize=256m。那么得到的将会是:
由于先设置的MaxNewSize的值倍Xmn的值覆盖,所以MaxNewSize等于512m。由于最后设置了NewSize也就是初始新生代大小,所以最终只改变了NewSize的值。
2)参数设置存在优先级问题。以-XX:NewSize、-XX:MaxNewSize、-Xmn与--XX:NewRatio为例。NewSize、MaxNewSize、Xmn的优先级高于RewRatio的设置。如果前三个参数其中任何一个与NewRatio同时存在于一个同一个jvm进程启动参数中的话。NewRatio设置参数值将会失效。
所以jvm启动参数请不要混乱使用,须对各参数理解其功能特性,否则会导致后续或者其他jvm调优人员解读起来产生歧义或产生错误的判断。
GC调优
一、通用调优参数
- -XX:+DisableExplicitGC 开关参数,开启此参数会禁用代码中的System.gc(),由于该方法默认会触发 FGC,并且忽略参数中的 UseG1GC 和 UseConcMarkSweepGC,因此必要时可以禁用该方法。
- -XX:+ExplicitGCInvokesConcurrent 开启此该参数可以改变代码中System.gc()的行为,也就是说,System.gc() 后不使用 FGC ,而是使用配置的并发收集器进行并发收集。注意:使用此选项就不要 使用DisableExplicitGC参数。
- -XX:PretenureSizeThreshold=n 除了年龄外,对象的体积也是影响晋升的一个关键,也就是大对象。如果一个对象新生代放不下,只能直接通过分配担保机制进入老年代。该参数是设置对象直接晋升到老年代的阈值,单位是字节。只要对象的大小大于此阈值,就会直接绕过新生代,直接进入老年代。注意:如果想使这个参数生效,请关闭UseAdaptiveSizePolicy参数。
- -XX:+ScavengeBeforeFullGC开关参数。在执行Full GC之前本身不会进行Minor GC,我们可以配置-XX:+ScavengeBeforeFullGC,让jvm进行Full GC之前先进行一次Minor GC。
- -XX:ParGCCardsPerStrideChunk=256 该参数控制GC工作线程的任务粒度。在Java中,一个card的大小是512 bytes,而ParGCCardsPerStrideChunk默认的值是256。那么一个Stride,或者MSDN文章中说的block,就是512 * 256 = 128K。假如你有4G的old gen,就会有4G / 128K = 32K的Strides在young gen collection的时候去扫描。这也就是为什么我前面说LinkedIn文章的interesting有些大惊小怪,呃,算法本来就是这个样子的啊。随着old gen大小增加,young gen collection就是要做更多的事情。ParGCCardsPerStrideChunk设置成多大,这个并不是仅仅由old gen的大小决定的,更大的决定因素是你的系统。有的文章也给出了32768的值,还有待验证。(该参数为诊断参数,需要-XX:+UnlockDiagnosticVMOptions参数来打开此隐藏参数)
- -XX:+UseGCOverheadLimit,开关参数。默认启动。如果在垃圾收集(GC)中花费的时间太多,则垃圾收集器将抛出OutOfMemoryError:如果在垃圾收集中花费的总时间超过98%,并且回收的堆少于2%,则将抛出OutOfMemoryError。
- -XX:UseAdaptiveSizePolicy,开关参数。默认开启。开关打开后就不需要手工指定新生代的大小(-Xmm)、Eden与Servivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式成为GC自适应的调剂策略。这也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
- -XX:ParallelRefProcEnabled,开关参数。该参数指定是否并行处理Reference对象。
二、垃圾收集器的组合策略
- -XX:+UseSerialGC参数可指定新生代Serial收集器,老年代Serial Old收集器。
- -XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用Serial Old收集器。(请不要在JDK1.8版本后续使用此参数)
- -XX:+UseConcMarkSweepGC:新生代使用ParNew收集器,老年代使用CMS收集器。
- -XX:+UseParallelGC:新生代使用ParallelGC收集器,老年代使用Serial Old收集器。
- -XX:+UseParallelOldGC:新生代使用ParallelGC收集器,老年代使用ParallelOldGC收集器。
- -XX:+UseG1GC:开关参数。用来开启和关闭G1收集器。
- -XX:+UnlockExperimentalVMOptions -XX:+UseZGC:参数组合可开启ZGC垃圾收集器。(注意:此为JDK11开始提供参数。目前ZGC是实验版本,而且只适合在Linux环境下使用)
三、各垃圾收集器调优
1. Serial收集器
截止目前JDK11版本。目前没有相关调优参数
2. ParNew收集器
- -XX:ParallelGCThreads={value} 这个参数是指定STW工作线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:3+((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用,不只是 ParNew。
3. Parallel Scavenge收集器
- -XX:ParallelGCThreads={value} 这个参数是指定STW工作线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:3+((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用。
- -XX:MaxGCPauseMillis 设置最大垃圾收集停顿时间,他的值是一个大于0的整数。ParallelGC 工作时,会调整 Java 堆大小或者其他的一些参数,尽可能的把停顿时间控制在 MaxGCPauseMillis 以内。如果为了将停顿时间设置的很小,将此值也设置的很小,那么 Parallel Scavenge 将会把堆设置的也很小,这将会导致频繁 GC ,虽然系统停顿时间小了,但总吞吐量下降了。
- -XX:GCTimeRatio 设置吞吐量大小,他的值是一个0 到100之间的整数,假设 GCTimeRatio 的值是 n ,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集,即不超过1% 的时间用于垃圾收集。
4. CMS收集器
- -XX:ConcGCThreads=n 设置并发标记线程数。将n设置为大约并行垃圾收集线程数(ParallelGCThreads)的1/4。(注意:网上其他文章把此参数定义为与ParallelGCThreads同样的设置结果是错误的。参考资料:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html与https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE与https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html。官方对于两个参数给出的是不同的解释。也可能是我的理解有误,此结论仅仅只是在官方文档中找到的答案,但并未在openjdk源码中得到证实)
- -XX:ParallelGCThreads={value} 这个参数是指定STW工作线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:3+((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用。
- -XX:+CMSPrecleaningEnabled,CMS预清理阶段开关参数。
- -XX:CMSInitiatingOccupancyFraction是一个0~100之间的整数。用来标记老年代使用比例达到某个比例时触发CMS垃圾收集器。JDK1.5默认值68。表示老年代使用空间达到68%时CMS垃圾收集器会被激活。如果老年代内存增长很快,建议降低阈值,避免 FGC,如果增长慢,则可以加大阈值,减少 CMS GC 次数。提高吞吐量。如果该值设置为-1的话,则由另外两个参数决定启动CMS收集器MinHeapFreeRatio、CMSTriggerRatio。((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 。
- -XX:+UseCMSCompactAtFullCollection 由于 CMS 使用标记清理算法,内存碎片无法避免。该参数指定每次 CMS 后进行一次碎片整理。(JDK8还在使用JDK9开始删除此参数)
- -XX:CMSFullGCsBeforeCompaction 由于每次进行碎片整理将会影响性能,你可以使用该参数设定多少次 CMS 后才进行一次碎片整理,也就是内存压缩。(JDK8还在使用JDK9开始删除此参数)
- -XX:+CMSClassUnloadingEnabled 允许对类元数(旧版本JVM中的永久代,JDK8版本以后的Metaspace区域)据进行回收。它会增加CMS remark的暂停时间,所以如果新类加载并不频繁,这个参数还是不开的好。
- -XX:+UseCMSInitiatingOccupancyOnly 开启此参数会关闭CMS自动计算垃圾收集机制。并且完全按照CMSInitiatingOccupancyFraction设置的值来启动CMS收集器。如果关闭此参数,CMSInitiatingOccupancyFraction的值则会变成一个参考值。
- -XX:CMSWaitDuration 数值参数,单位毫秒,默认值2000。此参数定义间隔N毫秒检测一次是否需要启动CMS垃圾收集。
- -XX:-CMSScavengeBeforeRemark开关参数。开启或关闭在CMS重新标记阶段之前的清除(YGC)尝试,CMS并发标记阶段与用户线程并发进行,此阶段会产生已经被标记了的对象又发生变化的情况,若打开此开关,可在一定程度上降低CMS重新标记阶段对上述“又发生变化”对象的扫描时间,当然,“清除尝试”也会消耗一些时间。
5. G1收集器
G1收集器的详细介绍请参阅:JVM Garbage First(G1)
调优参数:
- -XX:GCTimeRatio 设置吞吐量大小,他的值是一个0 到100之间的整数,假设 GCTimeRatio 的值是 n ,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集,即不超过1% 的时间用于垃圾收集。
- -XX:MaxGCPauseMillis=200默认值200毫秒。用于指定最大停顿时间,如果任何一次停顿超过这个设置值时,G1 就会尝试调整新生代和老年代的比例,调整堆大小,调整晋升年龄的手段,试图达到目标。和 PS 一样,停顿时间小了,对应的吞吐量也会变小。这点值得注意。使用MaxGCPauseMillis设置最大停顿时间,G1垃圾收集器使用一个预测模型来决定多少垃圾收集工作可以在最大停顿时间内完成。在垃圾收集工作完成的时候G1自动选择下一个需要垃圾收集目标也就是收集集合(Collect Set)。(官方给出的解释是,为了使G1能够尽最大可能的保证该参数最大停顿时间的工作性能,尽量不要手动设置新生代大小)
- -XX:GCPauseIntervalMillis 设置STW的间隔时间。
- -XX:G1HeapRegionSize=n 当使用G1收集器时,设置一个region区域的大小。这个大小范围在1M到32M之间,必须是2的次方。
- -XX:G1NewSizePercent=5 年轻代在堆中最小百分比,默认值是 5%(这是一个实验性参数experimental,如果使用需要附加参数-XX:+UnlockExperimentalVMOptions)
- -XX:G1MaxNewSizePercent=60 年轻代在堆中的最大百分比,默认值是 60%(这是一个实验性参数experimental,如果使用需要附加参数-XX:+UnlockExperimentalVMOptions)
- -XX:ParallelGCThreads={value} 这个参数是指定STW工作线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:3+((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用。
- -XX:ConcGCThreads=n 设置并发标记线程数。将n设置为大约并行垃圾收集线程数(ParallelGCThreads)的1/4。
- -XX:InitiatingHeapOccupancyPercent=45,数值参数。该参数决定了IHOP的初始值,IHOP可以决定当整个堆使用率达到多少时,触发并发标记阶段的执行。默认值时45,即当堆的使用率达到45%,执行并发标记阶段,该值一旦设置,始终都不会被 G1修改。也就是说,G1 就算为了满足 MaxGCPauseMillis 也不会修改此值。如果该值设置的很大,导致并发周期迟迟得不到启动,那么引起 FGC 的机率将会变大。如果过小,则会频繁标记,GC 线程抢占应用程序CPU 资源,性能将会下降。
- -XX:G1MixedGCLiveThresholdPercent=85 为混合垃圾回收周期中要包括的老年代区域设置占用率阈值。默认占用率为 85%。(这是一个实验性参数experimental,如果使用需要附加参数-XX:+UnlockExperimentalVMOptions)
- -XX:G1HeapWastePercent=5 该参数设置您愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比,Java HotSpot VM 不会启动混合垃圾回收周期。
- -XX:G1MixedGCCountTarget=8 该参数设置标记周期完成后,对存活数据上限为
G1MixedGCLIveThresholdPercent
的旧区域执行混合垃圾回收的目标次数。默认值是 8 次混合垃圾回收。混合回收的目标是要控制在此目标次数以内。 - -XX:G1OldCSetRegionThresholdPercent=10 该参数设置一个混合收集周期回收老年区域的回收上限。默认值是 Java 堆的 10%。(这是一个实验性参数experimental,如果使用需要附加参数-XX:+UnlockExperimentalVMOptions)
- -XX:G1ReservePercent=10 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认值是 10%。增加或减少百分比时,请确保对总的 Java 堆调整相同的量。
- -XX:G1RSetUpdatingPauseTimePercent=10 改参数代表G1修改RSet时间百分比。默认是目标停顿时间的10%。
- -XX:+G1EagerReclaimHumongousObjects,开关参数。默认开启。该参数指示了回收大对象(Humongous Objects)的时机。开启该参数,虚拟机会在任何STW的适当时候回收大对象。关闭该参数,虚拟机只会在Cleanup pause后与Full GC期间回收大对象。
- -XX:+G1UseAdaptiveIHOP,开关参数。默认开启。该参数表示是否启动自适应IHOP。如果启动自适应IHOP,虚拟机会在运行时自动调整IHOP值,也就是启动初始标记的阈值。
- -XX:+ReduceInitialCardMarks,开关参数。默认开启。分配对象时候的一个优化参数。在G1下如果分配较多的大对象。很可能造成停顿时间加长。可以尝试禁用此选项。慎用。
- -XX:G1RSetRegionEntries,数值参数。增大这个值可以有效的减少RSet的压缩coarsening的发生机率。
- -XX:ReferencesPerThread,数值参数。该参数决定了并发度:每N个Reference对象都会有一个线程参与Reference处理,这个值受限于-XX:ParallelGCThreads。如果设置为0,标明最大线程数总是-XX:ParallelGCThreads。这个决定了对 java.lang.Ref.* 这些对象实例的处理是否是多个线程并行执行的。
建议:
评估和微调 G1 GC 时,请记住以下建议:
- 年轻代大小:避免使用
-Xmn
选项或-XX:NewRatio
等其他相关选项手动设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标(MaxGCPauseMillis)。 - 暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1 GC 是增量垃圾回收器,暂停统一,同时应用程序线程的开销也更多。G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。如果将其与 Java HotSpot VM 的吞吐量回收器相比较,目标则是 99% 的应用程序时间和 1% 的垃圾回收时间。因此,当您评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。当您评估 G1 GC 的延迟时,请设置所需的(软)实时目标,G1 GC 会尽量满足。副作用是,吞吐量可能会受到影响。
- 掌握混合垃圾回收:当您调优混合垃圾回收时,请尝试以下选项。
-XX:InitiatingHeapOccupancyPercent
用于更改标记阈值。-XX:G1MixedGCLiveThresholdPercent
和-XX:G1HeapWastePercent
当您想要更改混合垃圾回收决定时。-XX:G1MixedGCCountTarget
和-XX:G1OldCSetRegionThresholdPercent
当您想要调整旧区域的 CSet 时。
多线程调优
通用调优
- -XX:ThreadStackSize 设置每个用户线程的堆栈大小。该参数与-Xss参数的设置效果是一样的。如果两个参数设置为0,则虚拟机自动设置为默认值,(此为各不同平台默认值:Linux/ARM (32-bit): 320 KB、Linux/i386 (32-bit): 320 KB、Linux/x64 (64-bit): 1024 KB、OS X (64-bit): 1024 KB、Oracle Solaris/i386 (32-bit): 320 KB、Oracle Solaris/x64 (64-bit): 1024 KB)
- -XX:ParallelGCThreads={value} 这个参数是垃圾收集器STW工作线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:3+((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用。
- -XX:ConcGCThreads=n 垃圾收集器并发标记阶段线程数。将n设置为大约并行垃圾收集线程数(ParallelGCThreads)的1/4。
注意:下面参数中英文资料太少。只找到下面这些很少的资料。但可以确定与GC垃圾收集工作线程有关系。具体的相关实现需要进一步查证。(中文资料https://www.cnblogs.com/leonxyzh/p/7484212.html ,https://www.jianshu.com/p/debef4f69a90英文资料https://engineering.linkedin.com/garbage-collection/garbage-collection-optimization-high-throughput-and-low-latency-java-applications 资料来源并非官方文档,而是LinkedIn在产品迭代中的实践结论)
- -XX:VMThreadStackSize 虚拟机内部线程大小。比如说STW线程或Concurrent Mark线程。
- -XX:CompilerThreadStackSize 虚拟机内部编译线程大小。
- -XX:ParGCStridesPerThread=2 控制每次worker thread整几个Strides (改参数为诊断参数,需要-XX:+UnlockDiagnosticVMOptions参数来打开此隐藏参数)。
- -XX:-BindGCTaskThreadsToCPUs 开关参数,默认关闭。在操作系统允许的情况下绑定GC线程到单个CPU。开启该参数可减少thread的switching。
- -XX:-UseGCTaskAffinity 开关参数,默认关闭。开启该参数保证每次worker thread被唤醒的时候争取拿到上次没有完成的工作。(该选项在JDK7和JDK8版本好像不起作用。参考文章:https://cloud.tencent.com/developer/article/1061456)
注意:下面参数与StackOverflowError异常有关系。
- -XX:StackShadowPages=7 一般只有用到本地方法时才会去修改这个值,或则默认值已经能够满足需求,它具体指本地方法与JAVA共享栈,这个值是指在内存单元上,JAVA能够够调用本地方法的最大范围,而这个StackShadowPages 就是这个范围的最大值,一般地如果改变这个值,需要同时修改-Xss 或者-XX:ThreadStackSize= 修改线程的默认栈大小。而这个值会影响到JVM里面最大的线程数量,所以调整需要小心。一般情况下,在应用程序中,没有调用本地方法时,JVM的这个参数StackShadowPages没有必要修改。
- -XX:StackYellowPages=3 线程栈尾受保护内存区域yellow page。当方法的执行在线程栈中到达yellow page。则抛出StackOverflowError异常,注意此时jvm进程不会挂掉。
- -XX:StackRedPages=1 线程栈尾受保护内存区域red page。当方法的执行在线程栈中到达red page。进程会退出,并产生Crash日志。(如果程序的执行期间,没有栈空间了,并且程序的执行超过了red page地址。jvm会直接Crash掉,并不会产生Crash日志。此时由于栈空间不足,虚拟机无法进入信号处理程序,所以无法产生Crash日志。由此可知,控制-XX:StackYellowPages与-XX:StackRedPages两个参数,在一定程度上有助于帮助定位问题并且有助于让程序避免Crash,这个程度是有限的。)
锁调优
1. 偏向锁
- -XX:+UseBiasedLocking 开关参数。该参数开启关闭偏向锁。默认开启。并发情况比较严重的应用中,建议关闭此选项。
- -XX:BiasedLockingStartupDelay=0 正常偏向锁在应用启动几秒后才会激活。但可设置此参数为0,可以关闭偏向锁启动延迟。
2.轻量级锁
目前未找到优化参数
3.自旋锁
jdk1.7开始取消了自旋锁的优化参数。虚拟机自行调整。在此不再详细说明。
4.锁消除
锁消除属于虚拟机在JIT编译时,对运行时上下文扫描运用逃逸技术,去除不可能存在共享资源竞争的锁的一种行为。所以想要开启锁消除必须同时使用如下两个参数。
- -XX:+DoEscapeAnalysis 开启逃逸分析
- -XX:+EliminateLocks 开启锁消除
5.重量级锁
- -XX:-UseHeavyMonitors 开关参数。默认关闭。开启此参数可以关闭偏向锁和轻量级锁。
JIT优化
JIT官方参考:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
- -Xcomp,强制虚拟机运行于编译模式。
- -Xint,强制虚拟机运行于解释模式。
- -Xmixed,混合模式执行。这也是Oracle Hotspot VM默认的执行模式。
- -XX:CompileThreshold,数值参数。默认10000。该参数为方法调用计数器热点代码编译阈值。Client模式下默认1500次。Server模式下默认10000次。(注意:开启-XX:+TieredCompilation分层编译策略。虚拟机会忽略此参数设置)。
- -XX:InterpreterProfilePercentage,解释器监控比率,数值参数。默认33。用来计算server模式下回边计数器热点代码编译阈值。(公式:方法调用计数器阈值(CompileThreshold)× (OSR 比率(OnStackReplacePercentage)- 解释器监控比率(InterpreterProfilePercentage)) / 100)。
- -XX:OnStackReplacePercentage,数值参数。默认140。该参数用来调整回边计数器热点代码编译阈值。(client模式下,方法调用计数器阈值(CompileThreshold)× OSR 比率(OnStackReplacePercentage)/ 100,其中 OnStackReplacePercentage 默认值为 933,如果都取默认值,那 client 模式虚拟机的回边计数器的阈值为 13995。server模式下,方法调用计数器阈值(CompileThreshold)× (OSR 比率(OnStackReplacePercentage)- 解释器监控比率(InterpreterProfilePercentage)) / 100,其中 OnStackReplacePercentage 默认值为 140,InterpreterProfilePercentage 默认值为 33,如果都取默认值,那 server 模式虚拟机回边计数器的阈值为 10700。)。
- -XX:+AggressiveOpts,开关参数。默认关闭。启用积极的性能优化功能。开启此选项之后,需要考虑到性能的提升,同样也需要考虑到性能提升所带来的不稳定风险。需要彼此权衡。
- -XX:+UseCounterDecay,开关参数。默认开启。关闭后可以禁止JIT调用计数器热度衰减。默认情况下,每次GC时会对调用计数器进行砍半的操作,导致有些方法一直是个温热,可能永远都达不到C2编译的1万次的阀值。
- -XX:+TieredCompilation,开关参数。默认开启。多层编译策略,在某些情况下因为有些方法C1编译后C2不再编译,多层编译反而比C2编译慢,如果发现此情况可进行禁止。(注意:开启此参数,虚拟机将会忽略-XX:CompileThreshold参数设置的值)
当启动分层编译时,虚拟机将不再使用-XX:CompileThreshold指定的阈值(该参数值失效),而是使用另一套阈值系统,这套阈值系统是根据当前待编译的方法数目动态调整的。
-------------------------------------------------------------------------------------
所谓动态调整并不复杂,在比较阈值时,Java虚拟机会将阈值与某个系数s相乘。该系数s与当前待编译的方法数目成正相关,与编译线程的数目成负相关。
系数s的计算方法为:
s = queue_size_X / (TierXLoadFeedback * compiler_count_X) + 1其中 X 是执行层次,可取 3 或者 4;
queue_size_X 是执行层次为 X 的待编译方法的数目;
TierXLoadFeedback 是预设好的参数,其中 Tier3LoadFeedback 为 5,Tier4LoadFeedback 为 3;
compiler_count_X 是层次 X 的编译线程数目。---------------------------------------------------------------------------------------
当启动分层编译时,即时编译具体触发条件如下:
当方法调用次数大于由参数 -XX:TierXInvocationThreshold 指定的阈值乘以系数,或者当方法调用次数大于由参数 -XX:TierXMINInvocationThreshold 指定的阈值乘以系数,并且方法调用次数和循环回边次数之和大于由参数 -XX:TierXCompileThreshold 指定的阈值乘以系数时,便会触发 X 层即时编译。
触发条件为:
i > TierXInvocationThreshold * s || (i > TierXMinInvocationThreshold * s && i + b > TierXCompileThreshold * s)其中i为方法调用计数器次数,b为回边调用计数器次数。
---------------------------------------------------------------------------------------
当启动分层编译时,触发OSR编译的阈值则是由参数-XX:TierXBackEdgeThreshold指定的阈值乘以系数。
---------------------------------------------------------------------------------------
如下为JDK11.0.2 LTS版本针对JIT编译器分层编译策略的相关优化参数。由于资料太少。所以在此只黏贴相对默认值?
intx Tier0BackedgeNotifyFreqLog = 10 {product} {default}
intx Tier0InvokeNotifyFreqLog = 7 {product} {default}
intx Tier0ProfilingStartPercentage = 200 {product} {default}
intx Tier23InlineeNotifyFreqLog = 20 {product} {default}
intx Tier2BackEdgeThreshold = 0 {product} {default}
intx Tier2BackedgeNotifyFreqLog = 14 {product} {default}
intx Tier2CompileThreshold = 0 {product} {default}
intx Tier2InvokeNotifyFreqLog = 11 {product} {default}
intx Tier3AOTBackEdgeThreshold = 120000 {product} {default}
intx Tier3AOTCompileThreshold = 15000 {product} {default}
intx Tier3AOTInvocationThreshold = 10000 {product} {default}
intx Tier3AOTMinInvocationThreshold = 1000 {product} {default}
intx Tier3BackEdgeThreshold = 60000 {product} {default}
intx Tier3BackedgeNotifyFreqLog = 13 {product} {default}
intx Tier3CompileThreshold = 2000 {product} {default}
intx Tier3DelayOff = 2 {product} {default}
intx Tier3DelayOn = 5 {product} {default}
intx Tier3InvocationThreshold = 200 {product} {default}
intx Tier3InvokeNotifyFreqLog = 10 {product} {default}
intx Tier3LoadFeedback = 5 {product} {default}
intx Tier3MinInvocationThreshold = 100 {product} {default}
intx Tier4BackEdgeThreshold = 40000 {product} {default}
intx Tier4CompileThreshold = 15000 {product} {default}
intx Tier4InvocationThreshold = 5000 {product} {default}
intx Tier4LoadFeedback = 3 {product} {default}
intx Tier4MinInvocationThreshold = 600 {product} {default}
bool TieredCompilation = true {pd product} {default}
intx TieredCompileTaskTimeout = 50 {product} {default}
intx TieredRateUpdateMaxTime = 25 {product} {default}
intx TieredRateUpdateMinTime = 1 {product} {default}
intx TieredStopAtLevel = 4 {product} {default}
- -XX:TieredStopAtLevel=4,数值参数。默认值4。可以通过该参数限制多层编译策略多高可以用到哪一层。(共五层0-4)。
- -XX:+UseJVMCICompiler,开关参数。默认关闭。JDK9开始支持参数。使用将JIT编译器的C2替换为Graal。(这是一个实验性参数experimental,如果使用需要附加参数-XX:+UnlockExperimentalVMOptions)
- -XX:+DoEscapeAnalysis,开关参数。默认开启。表示开启逃逸分析,分析对象作用域不会逃逸出某个作用域。开启此参数后JIT编译器通过上下文扫描证明对象无法逃离方法体或当前线程,则会对对象进行进一步优化(栈上分配、同步消除/锁消除、标量替换)。
- -XX:CICompilerCount=3,数值参数。该参数代表总编译线程数量。client模式下默认为1,server模式下默认为2,如果开启分层编译策略-XX:+TieredCompilation那么该参数默认值将会根据CPU逻辑核心数量扩展。如果手动设置该参数CICompilerCountPerCPU将被设置为false。
对于四核及以上的机器,总的编译线程的数目为:
n = log2(N) * log2(log2(N)) * 3 / 2
其中 N 为 CPU 核心数目。根据如上公式,四核CPU等于:2*1*3/2=3。也就是说四核CPU总编译线程数量为3。
- -XX:+CICompilerCountPerCPU,开关参数。默认开启。开启此参数编译线程数量CICompilerCount会根据CPU数量来调整。如果手动设置-XX:CICompilerCount参数的话。本参数将被设置为false。
- -XX:+BackgroundCompilation,开关参数。默认开启。也就是编译线程是后台运行的。如果关闭此参数,一旦达到 JIT 的编译条件,执行线程向虚拟机提交编译请求后将会一直等待,直到编译过程完成后再开始执行编译器输出的本地代码再继续执行方法或循环体。(也就是说如果关闭此参数,达到JIT编译条件的方法,不会与解释执行并发执行,必须等到JIT编译完成以后执行编译后的本地代码)。
- -XX:CodeCacheMinimumFreeSpace,数值参数。设置编译所需的最小可用空间(以字节为单位)。当剩余小于最小可用空间时,编译停止。(JDK8中可以使用此参数。JDK11没有看到此参数。)
- -XX:CompileCommand=command,method[,option],字符参数。该参数用于定制编译需求,比如过滤某个方法不做JIT编译,若未指定方法描述符,则对全部同名方法执行命令操作,可使用星号通配符(*)指定类或方法。该参数可多次指定,或使用 换行符(\n)分隔参数后的多个命令。
具体定制编译需求的命令:
exclude,跳过编译指定的方法
compileonly,只编译指定的方法
inline/dontinline,设置是否内联指定方法
print,打印生成的汇编代码
break,JVM以debug模式运行时,在方法编译开始处设置断点
quiet,不打印在此命令后续命令
log,排除-XX:+LogCompilation除指定方法之外的所有方法的编译日志记录(带选项)。默认情况下,对所有已编译的方法执行日志记录
option,此命令可用于将JIT编译选项传递给指定的方法以代替最后一个参数(选项)。编译选项在方法名称后面的末尾设置。您可以指定多个编译选项,以逗号或空格分隔。
其他命令:help详细的定制编译需求的命令用法请参照官方文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html,https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
详细的option选项命令使用参数请参阅官方文档:
- -XX:CompileCommandFile=filename,字符参数。设置从中读取JIT编译器命令的文件。默认情况下,该.hotspot_compiler文件用于存储JIT编译器执行的命令。命令文件中的每一行代表一个命令,一个类名和一个使用该命令的方法名。有关为JIT编译器指定要对方法执行的命令的更多信息,请参阅该-XX:CompileCommand选项。
- -XX:CompilerDirectivesFile=file,字符参数。该参数指定一个.json扩展名的指令文件添加到指令栈,以便于更精确的控制JIT的行为。(此参数为诊断参数,需要添加-XX:+UnlockDiagnosticVMOptions参数解锁此指令)。也可以通过jcmd指令对运行时jvm的指令栈进行控制而不需要重启jvm。
jcmd pid Compiler.directives_add file 添加指令文件到pid指定jvm指令栈栈顶。
jcmd pid Compiler.directives_remove 移除pid指定jvm指令栈栈顶指令块,可以重复执行直到剩下默认指令块。但无法删除默认指令快。
jcmd pid Compiler.directives_clear 移除pid指定jvm指令栈中全部指令块,但无法删除默认指令块。
jcmd pid Compiler.directives_print 打印pid指定jvm指令栈。
- -XX:CompileOnly=methods,字符参数。-请谨慎使用-。该参数限制jvm编译的方法列表(以逗号分隔)。
例如:
-XX:CompileOnly=java/lang/String.length,java/util/List.size。
该参数虽然不支持通配符,但是可以指定类或者包,进行全部编译。也可以指定方法名去编译所有类中方法。
例如:
-XX:CompileOnly=java/lang/String
-XX:CompileOnly=java/lang
-XX:CompileOnly=.length
- -XX:+Inline,开关参数。默认开启。该参数指定是否启用方法内联。
- -XX:InlineSmallCode,数值参数。该参数指定只有已编译后的方法小于此字节值,才会被JIT内联优化。
- -XX:MaxInlineLevel,数值参数。该参数指定内联最大嵌套数。
- -XX:MaxInlineSize,数值参数。该参数指定如果非热点方法的字节码大小超过该值,则无法内联。
- -XX:MinInliningThreshold,数值参数。该参数指定如果目标方法的调用次数低于该值,则无法内联。
- -XX:+InlineSynchronizedMethods,开关参数。默认开启。该参数指定是否允许内联同步方法。
- -XX:FreqInlineSize,数值参数。该参数指定如果热点方法的字节码大小超过该值,则无法内联。
- -XX:LiveNodeCountInliningCutoff,数值参数。方法内联编译过程中IR节点数目的上限。
- -XX:MaxNodeLimit,数值参数。该参数指定单个方法编译期间要使用的最大节点数。
- -XX:MaxTrivialSize,数值参数。该参数指定如果方法的字节码大小少于该值,则直接内联。
- -XX:+OptimizeStringConcat,开关参数。默认开启。该参数指定是否启动String串联操作优化。
- -XX:+UseAES,开关参数。默认开启。为Intel,AMD和SPARC硬件启用基于硬件的AES内在函数。Intel Westmere(2010及更新版本),AMD Bulldozer(2011及更新版本)以及SPARC(T4及更新版本)均为支持的硬件。UseAES与UseAESIntrinsics一起使用。
- -XX:+UseAESIntrinsics,开关参数。默认开启。默认情况下启用UseAES和UseAESIntrinsics标志,仅支持Java HotSpot Server VM 32位和64位。要禁用基于硬件的AES内在函数,请指定-XX:-UseAES -XX:-UseAESIntrinsics。但是如果选择使用AES指令,也同时需要两个参数同时使用。他们只支持jvm在server模式下运行。
- -XX:-UseCondCardMark,开关参数。默认关闭。在更新card table之前,可以检查卡是否已经标记。默认情况下禁用此选项,并且只应在具有多个套接字的计算机上使用此选项,从而提高严重依赖并发操作的Java应用程序的性能。在垃圾收集器marking阶段的一种优化方式,并与CMS收集器相关。但不影响G1。(由于官方把该参数放到了JIT高级优化参数里面,具体原因还未查明,故此处也将此参数暂时放在这里)。
- -XX:UseCodeCacheFlushing:开关参数,默认开启。当JIT编译的本地代码缓存区接近或达到ReservedCodeCacheSize时,为了编译更多方法,JIT编译器必须抛弃一些已经编译的方法。如果关闭此参数。当codecache缓冲区满时,JIT编译器将停止工作。
- -XX:+UseSHA,开关参数。默认开启。为SPARC硬件启用SHA加密散列函数的基于硬件的内在函数。UseSHA与结合使用UseSHA1Intrinsics,UseSHA256Intrinsics和UseSHA512Intrinsics选项。
- -XX:+UseSHA1Intrinsics,开关参数。默认关闭。为SHA-1加密哈希函数启用内在函数。
- -XX:+UseSHA256Intrinsics,开关参数。默认开启。为SHA-224和SHA-256加密哈希函数启用内在函数。
- -XX:+UseSHA512Intrinsics,开关参数。默认开启。为SHA-384和SHA-512加密散列函数启用内在函数。
- -XX:+UseSuperWord,开关参数。默认开启。允许将标量操作转换为超级字操作。默认情况下启用此选项。
- -XX:-UseRTMDeopt,开关参数。默认关闭。根据中止率自动调谐RTM锁定。该比率由-XX:RTMAbortRatio选项指定。如果中止事务的数量超过中止率,则包含锁定的方法将被取消优化并重新编译,并将所有锁定为正常锁定。默认情况下禁用此选项。-XX:+UseRTMLocking必须启用该选项。
- -XX:-UseRTMLocking,开关参数。默认关闭。为所有膨胀的锁生成受限制的事务性内存(RTM)锁定代码,使用正常的锁定机制作为回退处理程序。默认情况下禁用此选项。与RTM相关的选项仅适用于支持事务同步扩展Transactional Synchronization Extension(TSX)的x86 CPU上的Java HotSpot Server VM。
RTM是英特尔TSX的一部分,它是x86指令集扩展,有助于创建多线程应用程序。RTM引入了新的指令XBEGIN,XABORT,XEND,和XTEST。该XBEGIN和XEND说明附上一组指令作为一个事务中运行。如果在运行事务时未发现冲突,则内存和寄存器修改将在XEND指令处一起提交。该XABORT指令可用于显式中止事务以及XEND检查是否在事务中运行一组指令的指令。
当另一个线程尝试访问同一事务时,对事务的锁定会膨胀,从而阻止最初未请求访问该事务的线程。RTM要求在事务中止或失败时指定后备操作集。RTM锁是一种委托给TSX系统的锁。
RTM提高了在关键区域中具有低冲突的高竞争锁的性能(这是不能同时由多个线程访问的代码)。RTM还提高了粗粒度锁定的性能,这在多线程应用程序中通常表现不佳。(粗粒度锁定是长时间保持锁定以最小化获取和释放锁定的开销的策略,而细粒度锁定是通过仅在必要时锁定并尽快解锁来尝试实现最大并行性的策略。此外,对于不同线程使用的轻度争用锁,RTM可以减少错误的缓存行共享,也称为缓存行乒乓。当来自不同处理器的多个线程访问不同的资源时会发生 但资源共享相同的缓存行。结果,处理器重复地使其他处理器的高速缓存行无效,这迫使它们从主存储器而不是它们的高速缓存读取
针对于RTM(Restricted Transactional Memory)介绍最好的一片文章:JDK-8031320
- -XX:RTMAbortRatio,数值参数。RTM中止比率指定为所有已执行RTM事务的百分比(%)。如果许多中止事务变得大于此比率,则编译后的代码将被去优化。-XX:+UseRTMDeopt启用该选项时将使用此比率。此选项的默认值为50.这意味着如果50%的所有事务都被中止,则编译后的代码将被去优化。
- -XX:RTMLockingCalculationDelay,数值参数。该参数指定延迟终止比率计算时间,单位毫秒。
- -XX:RTMRetryCount,数值参数。RTM锁定代码将在中止或忙碌时重试此选项指定的次数,然后再回退到正常锁定机制。此选项的默认值为5。-XX:UseRTMLocking必须启用该选项。
- -XX:AllocateInstancePrefetchLines=1,数值参数。设置在实例分配指针之前预取的行数。默认情况下,预取的行数设置为1。
- -XX:AllocatePrefetchDistance,数值参数。设置对象分配的预取距离的大小(以字节为单位)。将从最后分配的对象的地址开始预取将要使用新对象的值写入的内存。每个Java线程都有自己的分配点。负值表示基于平台选择预取距离。正值是预取的字节数。
- -XX:AllocatePrefetchInstr,数值参数。将预取指令设置为在分配指针之前预取。只有Java热点服务器VM支持此选项。可能的值从0到3。值后面的实际指令取决于平台。默认情况下为0。
- -XX:AllocatePrefetchLines,数值参数。使用编译代码中生成的预取指令设置在最后一次对象分配后要加载的高速缓存行数。如果最后分配的对象是实例,则默认值为1;如果是数组,则默认值为3。
- -XX:AllocatePrefetchStepSize,数值参数。设置顺序预取指令的步长。
- -XX:AllocatePrefetchStyle,数值参数。为预取指令设置生成代码样式。的风格参数是从0至3的整数。(0,不生成预取指令。1,每次分配后执行预取指令,默认值。2,使用TLAB水印指针来确定何时执行预取指令。3,在SPARC上使用BIS指令进行分配预取。)
调试与问题排查
jvm调试控制参数
- -XX:MaxJavaStackTraceDepth=1024 当发生StackOverflowError异常的时候,打印到日志里面的栈信息有一定可能性不是全的。必要的时候可以调整此值来保障栈信息的完整性。可以以将此值设置为-1,此时将会打印全部堆栈。
- -XX:-PrintCompilation,开关参数。默认关闭。开启此参数可以打印应用中JIT编译情况。打印内容:第一列是时间。第二列是Java虚拟机维护的编译ID。接下来是标识:%(是否OSR编译),s(是否synchronized方法),!(是否包含异常处理器),b(是否租塞了应用线程,显示了b则代表编译线程不是后台运行,可了解一下参数-Xbatch),n(是否为native方法)。再接下来则是编译层次,以及方法名。如果是OSR编译,那么方法名后面还会跟着@以及循环所在的字节码。
- -XX:-PrintGC,开关参数。默认关闭。为应用打印GC日志。
- -XX:-PrintGCDetails,开关参数。默认关闭。为应用打印GC详细日志。
- -Xloggc,字符参数,指定gc日志输出位置。例如:-Xloggc:/home/rock/gc_%t.log
- -Xlog,jdk11参数。该参数可以支持多种日志打印,例如gc日志(-Xlog:gc*:file=/home/rock/gc_%t_%p.log::filecount=5,filesize=20480),由于内容比较多。更详细的描述请参照官方文档https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-BE93ABDC-999C-4CB5-A88B-1994AAAC74D5
- -XX:-LogCompilation,开关参数。默认关闭。开启此参数会记录JIT编译活动到当前工作目录中名为hotspot.log的文件中,也可以使用-XX:LogFile参数指定输出文件路径。(该参数为诊断参数,需要-XX:+UnlockDiagnosticVMOptions参数来打开此隐藏参数)。您也可以使用-XX:+PrintCompilation参数,将冗长的JIT编译信息打印到控制台。
- -XX:-PrintInlining,开关参数。默认关闭。打印方法获得内联的信息到控制台。
- -XX:LogFile,字符参数。指定日志数据的写入文件的绝对路径。如果不指定,默认情况下,会在当前工作目录创建hotspot.log文件。
- -XX:-PrintAssembly,开关参数。默认关闭。打印装配字节码的native方法,需要外部链接库disassembler.so。
- -XX:-HeapDumpOnOutOfMemoryError,开关参数。默认关闭。启动该参数后发生OOM时自动导出heap信息。
- -XX:-HeapDumpAfterFullGC,开关参数。默认关闭。启动该参数后发生FullGC后自动导出heap信息。
- -XX:-HeapDumpBeforeFullGC,开关参数。默认关闭。启动该参数后发生FullGC前自动导出heap信息。
- -XX:HeapDumpPath,字符参数。该参数指定heapdump文件输出位置。
- -XX:-VerifyBeforeGC,开关参数。默认关闭。该参数是在GC前进行校验的参数,在校验过程中当发生地址异常的化会打印出异常的地址,并且让JVM crash,因为这个参数每一次GC都要检查,包括新生代的GC,影响一定的性能,并不适合在产品环境中使用,但对发现GC中的对象问题,却非常有帮助。
- -XX:ErrorFile,字符参数。该参数指定jvm crash时候的错误信息输出位置。例如:-XX:ErrorFile=/path/hs_error%p.log,其中“%p”用来获取当前进程ID。
- -XX:OnError,字符参数。该参数指定jvm crash时候执行命令行。每个命令以“;”分隔。
- -XX:-ShowMessageBoxOnError,开关参数。默认关闭。当jvm crash的时候在linux里会启动gdb 去分析和调式,适合在测试环境中使用。
- -XX:NativeMemoryTracking=[off | summary | detail],字符选项参数。默认off。该参数用来开启NMT功能,用来排查堆外内存使用情况。虚拟机启动后可以使用“jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]”命令查看。(开启NMT会有5%-10%的性能耗损)
- -XX:-PrintNMTStatistics,开关参数。默认关闭。该参数指定在开启了NativeMemoryTracking参数后,在jvm退出时控制台打印NMT信息。
- -XX:G1SummarizeRSetStatsPeriod,数值参数。该参数指定了G1的RSet的统计周期,也就是经过多少次GC后进行一次统计。在JDK11下使用日志gc+remset=trace选项配合该参数,可以查看是否发生过RSet压缩coarsening。(该参数为诊断参数,需要-XX:+UnlockDiagnosticVMOptions参数来打开此隐藏参数)
几种OOM介绍
- java.lang.OutOfMemoryError: Java heap space:
这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有对象创建没有销毁也就是错误的代码书写逻辑;
如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决:
< jvm-arg>-Xms3062m < / jvm-arg>
< jvm-arg>-Xmx3062m < / jvm-arg>
- java.lang.OutOfMemoryError: GC overhead limit exceeded
【解释】:JDK6新增错误类型,当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。
【解决方案】:
1、查看系统是否有使用大内存的代码或死循环;
2、通过添加JVM配置,来限制使用内存:
< jvm-arg>-XX:-UseGCOverheadLimit< /jvm-arg>
- java.lang.OutOfMemoryError: Metaspace:
这种是P区内存不够,可通过调整JVM的配置:
< jvm-arg>-XX:MaxMetaspaceSize=128m< /jvm-arg>
- java.lang.OutOfMemoryError: Direct buffer memory:
调整-XX:MaxDirectMemorySize= 参数:
< jvm-arg>-XX:MaxDirectMemorySize=128m< /jvm-arg>
- java.lang.OutOfMemoryError: unable to create new native thread
【原因】:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。
【解决】:
1.通过 -Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);
2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。
- java.lang.StackOverflowError
【原因】:这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。
【解决】:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。
jvm crash & heap dump & core dump & High CPU
模拟High CPU----分析
这个模拟很容易,简单的无限循环就可以做到。
使用top命令查找high cpu的进程:
使用top -p 1997 -H命令查找high cpu的线程:
转换2044为十六进制7fc。使用jstack -l 1977>jstack.1977.log命令导出thread dump。然后再文档中找到本地线程id为7fc的线程。可以很明显的看到当前线程执行的方法和正在做的high cpu的事情。
core dump分析
- gdb分析:
- jstack分析:(注意下面图片中的命令,在jdk11环境下需要借助jdk8的java命令才可以直接分析core文件。如果直接使用jdk11的java命令会提示:sun.jvm.hotspot.debugger.DebuggerException: cannot open binary file。)
- jmap分析:(注意下面图片中的命令,在jdk11环境下需要借助jdk8的java命令才可以直接分析core文件。如果直接使用jdk11的java命令会提示:sun.jvm.hotspot.debugger.DebuggerException: cannot open binary file。)
模拟jvm crash使用CrashAnalysis帮助分析crash 文件,简单例子:
想要模拟jvm crash并不容易,因为普通的java异常都已经被程序员、应用层、虚拟机拦截到了。所以想要模拟jvm crash必须要C/C++代码的配合。咱们先看代码:
helloworld.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_com_test_rock_oom_TestCrash1
#define _Included_com_test_rock_oom_TestCrash1
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_rock_oom_TestCrash1_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
helloworld.cpp文件:
#include "HelloWorld.h"
#include <stdio.h>
#include <jni.h>
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_rock_oom_TestCrash1_displayHelloWorld
(JNIEnv *, jobject)
{
int* p = NULL;
*p = 3;
//非法地址操作异常
printf("Hello World!\n");
return;
}
TestCrash1.java文件:
package com.test.rock.oom;
/**
* 测试jvm crash
* 启动时增加如下参数,指定crash日志打印位置
* -XX:ErrorFile="C:\workspaces\cresh_%p.log"
*/
public class TestCrash1 {
public native void displayHelloWorld();// java native方法申明
static {
System.load("C:/qt_workspace/build-helloworld-Desktop_Qt_5_12_2_MinGW_64_bit-Release/release/helloworld.dll");
}
public static void main(String[] args) {
Thread t = new Thread(()->{
TestCrash1 helloWorld = new TestCrash1();
helloWorld.displayHelloWorld();
});
t.start();
}
}
下面我们来看CrashAnalysis的分析结论:
这是首页,大概分析了问题的可能性:
这个页面指定了出现问题的dll文件:
这个页面指定了出现问题的线程以及堆栈信息:
通过分析crash的log文件我们还是可以进一步的查询到更详细的信息的:
这里指定了有关crash的java代码部分可以很明显的定位:
crash问题总结:crash有很大一部分原因是C/C++语言部分出现了问题导致的异常崩溃。换句话说在运行时角度上来讲大部分原因是机器码部分出现了问题导致了crash问题(我们都知道jvm运行分为java字节码解释执行和JIT编译器编译后的机器码的直接执行)。所以说JIT编译器编译后的机器码也有可能会导致此类异常,如果由于JIT编译器导致了crash,那就需要详细排查目标java代码的编写所涉及到的上下文信息了。也可以使用临时解决方案,JIT优化参数:-XX:CompileCommand的exclude指令,临时排除掉优化方法即可。
另附上linux下编译过程:
g++ helloworld.cpp -c
gcc -shared helloworld.o -o libHelloworld.so
模拟OOM使用eclipse MAT分析heap dump文件,简单例子:
测试代码:
package com.test.rock.oom;
import java.util.ArrayList;
/**
* 测试oom
* 启动时增加如下参数使程序发生oom的时候可以打印堆栈信息到磁盘
* -XX:+HeapDumpOnOutOfMemoryError
*/
public class TestCrash {
private TestCrash t1 = null;
public static void main(String[] args){
ArrayList<StubClass> list = new ArrayList<StubClass>();
int count = 1000000000;
for (int i = 0; i < count; i++) {
StubClass bj = new StubClass();
list.add(bj);
}
}
static class StubClass {
private Integer n = new Integer(123334);
private Integer m = new Integer(123334);
public void print() {
System.out.println("m="+m);
System.out.println("n="+n);
}
}
}
点击默认报告页面的See stacktrace。
从这个页面可以看出,出现了OOM异常。目标类是我们的测试类TestCrash.class。
在Overview页面点击Dominator Tree。
看到占用内存比例比较大的一项,继续点击向下查找。
这里可以看到,很明显是我们的测试类中的内部类StubClass.class创建过多导致了OOM异常的发生。
jdk命令行调试工具
1)jmap -histo:live <pid>
命令可以手动启动Full GC
2) jps [options] [hostid]
jps主要用来输出JVM中运行的进程状态信息。
-q 输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数
如下查看运行的java进程信息,打印jar名以及运行main方法传入的参数:
3) jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]
jstat命令可以用于持续观察虚拟机内存中各个分区的使用率以及GC的统计数据。vmid是Java虚拟机ID,在Linux/Unix系统取进程ID。
如下面输出的信息,采样时间间隔为1000ms,采样5次:
上述各个列的含义:
- S0C、S1C、S0U、S1U:young代的Survivor 0/1区容量(Capacity)和使用量(Used)。0是FromSurvivor,1是ToSurvivor。
- EC、EU:Eden区容量和使用量
- OC、OU:年老代容量和使用量
- MC、MU:元数据区(Metaspace)已经committed的内存空间和使用量
- CCSC、CCSU:压缩Class(Compressed class space)committed的内存空间和使用量。
- YGC、YGT:young代GC次数和GC耗时
- FGC、FGCT:Full GC次数和Full GC耗时
- CGC 和 CGCT,它们分别代表并发 GC STW 的次数和时间
- GCT:GC总耗时
可以通过分区占用量上看到,在第2-3秒之间发生了一次YGC。YGC次数+1,并且Survivor from区的内存空间从1233.7->0,Survivor from从0->1536。Eden区也释放了很多内存空间。其他变化的空间占用也有元数据区以及元数据区的压缩Class区。Compressed class space也是元数据区的一部分,默认是1G,也可以关闭。具体的jvm8内存分布不再详述。下一篇GC优化会再展开整理下。
如果只看gc的总统计信息,也可以用jstat -gcutil vmid查询:
4) jmap [option] pid
- jhsdb jmap --heap --pid <pid>或者jmap -heap <pid>查看heap情况
可以用来查看堆内存的使用详情。
- jmap -dump:format=b,file=jmap.hprof <pid>
生成内存快照文件,内存快照文件可以使用eclipse MAT工具分析。
5)jstack [option] pid
jstack可以用来查看Java进程内的线程堆栈信息。
-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
注意:当进程CPU使用率过高而导致无法打印线程堆栈信息时,可以使用-F参数强行打印。
例如:jstack -F 23040或者jhsdb jstack --pid 23040
调试时的坑
- 执行jmap异常 operation not permitted
vim /etc/sysctl.d/10-ptrace.conf
kernel.yama.ptrace_scope = 0
- coredump
ulimit -c unlimited
linux命令行调试工具
top
排查问题常用按键:
e:切换列表部分数值单位(k,m,g,t,p)
E:切换统计部分数值单位(k,m,g,t,p,e)
M:按照内存占用率排序列表
P:按照CPU占用率排序列表
常用列说明:
PID 进程ID
USER 进程所有者用户名
PR 优先级
NI nice值,负值表示高优先级
VIRT 进程使用的虚拟内存总量,VIRT=SWAP+RES
RES 进程使用的、未被换出的物理内存大小,RES=CODE+DATA
SHR 共享内存大小
S 进程状态,(D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)
%CPU 上次更新到现在的CPU时间占用百分比
%MEM 进程使用的物理内存百分比
TIME+ 进程使用的CPU时间总计
COMMAND命令名/命令行
SWAP 进程使用的虚拟内存中,被换出的大小
CODE 可执行代码占用的物理内存大小
DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小
pmap <pid>
第一列,内存块起始地址
第二列,占用内存大小
第三列,内存权限
第四列,内存名称,anon表示动态分配的内存,stack表示栈内存
最后一行,占用内存总大小,请注意,此处为虚拟内存大小,占用的物理内存大小可以通过top查看
BTrace监控调试
一张图看懂BTrace工作原理
描述
BTrace官网:https://github.com/btraceio/btrace
BTrace是一个可以在不停止目标jvm进程,不修改java源代码的基础上对生产环境服务器上的jvm进程进行监控和测试。他使用ASM,instrument,JVMTI,Java Compiler Api等技术直接修改正在运行中java字节码信息,可以监控所有java方法的入参和出参。可以大大减少定位问题和问题排查的时间。虽然功能强大,但也有弊端,无法远程调试。但是对于排查问题,获取生产环境的关键数据、复现和定位问题来讲。这些弊端已经算得上非常小了。因为如果问题不能够在开发环境或测试环境复现且必须要在生产环境获取必要数据,但是数据库以及程序员写的log又不能满足要求的时候,BTrace工具就显得格外的抢眼了(毕竟出了问题,就意味着下次重启jvm进程的时间已经很近了,而且现在哪一家公司的生产环境怎么会负载一台机器运行呢?)。对于生产环境大部分的问题,只有运行中的程序数据才可以代表一切,而在开发人员手中静止的代码是没有办法解决所有问题的。
BTrace的功能强大请参见官网提供的样例代码:https://github.com/btraceio/btrace/tree/master/samples
支持jdk11
BTrace官方的版本只支持到jdk8。本人亲测在jdk1.8.0_201版本下可以运行官方版本。为了能够在后续版本中使用。小编修改了BTrace源代码和ASM源代码,才保证在jdk11下通畅运行。主要问题在于jdk9以后出现的模块化技术与越来越严格的jdk内部类的使用权限、还有在于官方的btrace-asm-5.0.4.jar是基于asm-all-5.2.jar,所以asm版本有点低,支持的jdk版本太低了。当然了也没这么简单,小编在这里不再详述了。小编在这里提供了支持jdk11版本的资源供大家下载,linux系统覆盖btrace、btracec文件和三个jar,win系统覆盖btrace.bat、btracec.bat文件和三个jar即可使用。小编亲测在win10 x64专业版、Ubuntu18.04.2LTS两个系统,jdk1.8.0_201和jdk11.0.2 LTS两个版本。均已跑通所有官方提供的BtraceScript样例代码(排除DTrace相关)。
jdk11小测试
测试java代码
public class TestBt {
public static void main(String[] args){
new Thread(()->{
TestBt tb = new TestBt();
int icount = 0;
while(true){
try{
Thread.sleep(1000 * 10);
tb.thread_process(icount++);
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
public String thread_process(int icount){
System.out.println(icount);
return "汉字:" + icount;
}
}
Btrace测试脚本
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.println;
@BTrace
public class BtraceScript {
@OnMethod(
clazz = "TestBt",
method = "thread_process",
location = @Location(Kind.RETURN)
)
public static void thread_process(int icount, @Return String result) {
println("icount: " + icount);
println(result);
}
}
windows测试--目标进程
windows测试--监控进程
linux测试--目标进程
linux测试--监控进程
webBTraceUtil小工具
一张图看懂小工具工作原理
小工具页面
使用须知:
- connect按钮用来启动socket链接
- Clean Up和Clean Up All两个按钮用来停止并清除BTrace测试脚本的任务。这点很重要。这就好比使用BTrace官方版命令行Ctrl+C,然后选择1。是一样的效果。目的是用来把已经在目标jvm中运行Class字节码再次修改回来。我们都知道BTrace是通过各种技术修改正在运行中java字节码信息达到目的的。我们如果没有在退出的时候修改回来,这个被修改的Class信息会永远停留在目标jvm中。即便如此官方BTrace也添加了有效的ShutdownHook与Signal(SIGINT)。所以网上其他文章或教程等所说的BTrace修改的Class字节码信息会永远停留在目标jvm进程中的这个观点是错误的。小编在这里为官方BTrace“平反”呵呵。我们再说回这两个按钮,Clean Up的作用是停止并清除当前页面发起的BTrace测试脚本任务,Clean Up All的作用是停止并清除所有页面发起的BTrace测试脚本任务。(如果大家不小心关闭页面,或者刷新页面的话。不用担心。小工具会自动停止并清除当前页面发起的BTrace测试脚本任务。或者大家可以不用点击这两个按钮,直接关闭页面即可,也可以达到Clean Up的效果。)
对于小编为官方BTrace“平反”的证据如下:主要涉及到BTrace官方源代码com.sun.btrace.agent.Client类中:retransformLoaded()与OnExit()两个方法。另外一个重要的类com.sun.btrace.runtime.BTraceTransformer类的transform()方法。在一个BTrace脚本的生命周期中,会执行两次retransformLoaded()方法,第一次的目的是为了修改目标Class的字节码是为了测试数据。第二次执行目的是为了修正回原来类的字节码。
Client.OnExit()方法中cleanupTransformers()方法的执行清除了目标类过滤器。BTraceTransformer.transform()方法,会在if(filter.matchClass******这一行返回NULL。
引用Oracle官方:If the implementing method determines that no transformations are needed, it should return
null
. Otherwise, it should create a newbyte[]
array, copy the inputclassfileBuffer
into it, along with all desired transformations, and return the new array. The inputclassfileBuffer
must not be modified.详见:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html
小编在这里不再多说了。请参考BTrace官方源代码,或Oracle官方对于ClassFileTransformer.transform()方法的描述。再或者开启BTrace的debug模式查看详细的日志输出。再~再~再~在不行,请自行编写instrumentation相关技术代码自己测试或者参见小编写的测试代码:https://pan.baidu.com/s/1vRT8lGSoi1bsDNWNuo-UCQ 提取码 r7n2。
- 小编在这里再剔除网上的另一个谣言。BTrace官方版本本来就支持远程调试,对于这一点不需要修改任何源代码。(即便如此,小编在此也不建议大家在生产环境下进行远程调试)
java -javaagent:"C:\电脑搬家资料拷贝-重要\类库\btrace\btrace1\build\btrace-agent.jar"=port=2020,statsd=,debug=true,bootClassPath=.,probeDescPath=. --add exports=java.base/jdk.internal.misc=ALL-UNNAMED TestBt
这里请注意:虽然远程调试的时候PID没有任何意义。但是如果没有会报错。
- debug开关按钮,功能等同于命令行-v的选项开关。小编自认为debug功能比较有用所以显示的添加了一个按钮。开启此功能,debug信息会打印到目标JVM进程控制台与webBTraceUtil小工具控制台。
- 当BTrace脚本中有OnEvent注释的时候,可以使用event按钮发送指定事件到agent端。窗口中输入字符,等同于官方命令行Ctrl+C选择3的情况。窗口中不输入字符,等同于官方命令行Ctrl+C选择2的情况。
- BTrace脚本请输入在最上面的textarea中。
- BTrace命令行命令,请输入在BTrace command line输入框中。
1. 输入规则是剔除原命令行中btrace字符和脚本文件的输入即可,其他规则和命令行的一样。
例如:
命令行:btrace 8952 BtraceScript.java 小工具:8952
命令行:btrace --version 小工具:--version
命令行:btrace -v 8952 BtraceScript.java 小工具:-v 89522. 小工具剔除了-I选项。此选项在小工具中不起作用。
3. webBTraceUtil的BTrace command line命令行输入框中,没有官方原命令行中的引号那么一说,可以直接省略。这点请注意。
例如:这里的classpath路径不需要引号
- GO按钮的作用是发送BTrace脚本以及BTrace命令到小工具后台并启动测试任务。如果重复点击GO按钮,小工具会自动停止并清除掉上一个本页面发起的测试脚本任务,并执行新的测试任务。
- 当前页面发起的测试脚本任务返回信息会显示在最先面的textarea中。
- 本小工具支持多人同时测试,互不影响。但也要注意,请给生产环境留下喘气的时间。
- 小工具不支持Dtrace测试。请使用官方版本或小编提供的支持jdk11版本。
- 使用webBTraceUtil小工具,不需要安装BTrace官方版本,也不需要配置任何BTRACEHOME环境变量。除了以下注意事项以外直接拷贝直接使用。
注意:
jdk1.8版本请拷贝JAVA_HOME/lib目录下的tools.jar。到webBTraceUtil/lib目录下。注意这里一定是测试目标机器的tools.jar。win版本jdk的tools.jar无法在linux版本jdk下使用。
jdk11版本,不需要拷贝tools.jar因为没有。但是所测试的目标JVM进程,启动参数必须添加--add-exports java.base/jdk.internal.misc=ALL-UNNAMED否则无法运行。
小编提供了两个版本web小工具,一个支持jdk1.8一个支持jdk11。
资源:webBTraceUtil_for_JDK11 webBTraceUtil_for_JDK1.8
BTrace总结:
- 本文所有内容,以及小编提供的支持jdk11版本的BTrace,和两个BTrace的web小工具。都是基于官方BTrace1.3.11.3版本的源代码。
- 想要正确从目标jvm进程获取汉字不乱码。就必须在目标JVM所运行的class字节码编译的时候使用utf-8格式编译。而BTrace脚本文件也必须使用utf-8编码格式。javac -encoding utf-8 TestBt.java
- 在JDK11下运行程序,目标jvm进程启动时需要增加--add-exports java.base/jdk.internal.misc=ALL-UNNAMED参数。因为agent.jar使用了一些jdk.internal.misc包下的类。
- 由于BTrace的agaent端和client端使用Socket技术通信。所以建议不要进行远程监控调试。(建议直接使用官方的本地BTrace脚本调试方式或者小编提供的webBTraceUtil小工具)
- BTrace默认会占用服务器2020端口。请注意服务器端口或修改。
- 请使用对应版本,在jdk1.8下编译出来的BTrace无法在jdk11运行并attach到jdk11的JVM进程。
- 有任何问题,请加小编微信:longyunjiangliang。小编也希望能够与您有更多的技术交流。
可视化的GC日志分析
可很直观的查看到GC调优重要性能指标----吞吐量、STW时间。
GCEasy
GCViewer
图形界面性能监控工具
开启远程监控-JMX
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
开启远程监控-jstatd
- JDK11
grant codebase "jrt:/jdk.jstatd" {
permission java.security.AllPermission;
};
grant codebase "jrt:/jdk.internal.jvmstat" {
permission java.security.AllPermission;
};
- JDK8
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
启动RMI服务:jstatd -J-Djava.rmi.server.hostname=192.168.1.8 -J-Djava.security.policy=./jstatd.jdk11.policy -p 1100
VisualVM
- File->Load->Thread Dump(对应的是jstack生成的线程栈文件)
- File->Load->Heap Dump(对应的是jmap生成的堆栈文件)
JDK Mission Control 7.1.0
最新JMS7.1.0编译并运行:
第一:下载安装版本控制mercurial
Mac OS X:brew install mercurial
Windows : https://www.mercurial-scm.org/wiki/Download
linux :sudo apt-get install mercurial
第二:下载JMC源代码
hg clone http://hg.openjdk.java.net/jmc/jmc/
第三:安装Maven
https://maven.apache.org/install.html
第四:编译(此处需要jdk8或后续版本,小编使用jdk11.0.2编译通过)。此过程首次分为三个小步骤,后续编译只需要最后一步
打开一个命令行窗口:
- cd releng/third-party
mvn p2:site
mvn jetty:run开启一个新的命令行窗口:
- cd core
mvn install- mvn clean package
第五:运行
Windows:target\products\org.openjdk.jmc\win32\win32\x86_64\jmc.exe -vm %JAVA_HOME%\bin
Mac OS X:target/products/org.openjdk.jmc/macosx/cocoa/x86_64/JDK\ Mission\ Control.app/Contents/MacOS/jmc -vm $JAVA_HOME/bin
JDK 11:
-XX:StartFlightRecording
JDK 9,JDK 10:
-XX:+UnlockCommercialFeatures -XX:StartFlightRecording
JDK 8u40,
-XX:+UnlockCommercialFeatures -XX:StartFlightRecording=name=whatever
JDK 7u4,JDK 8u20:
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=name=whatever
如果要避免全部附加到进程,可以指定一个文件名,即-XX:StartFlightRecording=filename=rec.jfr,然后通过设置持续时间(即工期=60)转储记录,或者在JVM退出:
-XX:StartFlightRecording= filename=rec.jfr,dumponexit=true
可以在JMC中打开然后打开记录文件,在JDK 11中,这已进一步简化,因此只需指定:
-XX:StartFlightRecording:filename=c:\recs
并且文件名将在指定的目录中生成,并在JVM退出时自动转储
内容来自:https://cloud.tencent.com/developer/ask/151108/answer/262468
JProfile
https://download.csdn.net/download/jl19861101/11078717
总结
JVM性能调优思路
(本文只从理论而不是实际效果的角度,客观的评论方式方法的存在)
1.内存调优
2.GC调优
3.多线程调优
4.JIT调优
JVM生产环境问题排查思路
(本文只从JVM层面阐述关注点,不包括程序员在代码层面以及开发阶段的问题排查方式方法)
参考资料:
https://docs.oracle.com/javase/9/migrate/toc.htm#JSMIG-GUID-9E847B7E-1F6B-4AD4-A5EE-66F8EF8BA9F6
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html
http://openjdk.java.net/jeps/197
https://blog.csdn.net/qq_22194659/article/details/85245144
https://blog.csdn.net/jl19861101/article/details/88566316
https://blog.csdn.net/foolishandstupid/article/details/77596050
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#large_pages
《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》
《实战Java虚拟机:JVM故障诊断与性能优化》
《Java性能优化权威指南》