JVM性能调优

编译器

查询运行模式

java -version
截屏2022-09-22 上午10.56.26.png
  • mixed mode 混合模式
    设置编译模式
java -Xint -version
截屏2022-09-22 上午10.58.50.png
  • -Xint :设置JVM的执行模式为解释执行模式
  • -Xcomp : JVM优先以编译模式运行,不能编译的,以解释模式运行。
  • -Xmixed :混合模式运行

一般情况下

一开始一 般由解释器解释执行
当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会认为这些代码是 “热点代码”。为了提高热点代码的执行效率,会用即时编译器(也就是JIT )把这些热点代码编译成与本地平台相关的机器码,并进行各层次的优化

Hotspot的即时编译器

C1编译器:

是一个简单快速的编译器,主要关注局部性的优化,适用于执行时间较短或对启动性能有要求的程序。例如,GUI,应用对界面启动速度就有一定要求。
也被称为是Client Compiler

C2编译器:

是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序
也被称为是Server Compiler

并进行各层次的优化
分层编译

  • 0 :解释执行
  • 1 :简单C1编译:会用C1编译器进行一些简单的优化 ,不开启Profiling
  • 2 :受限的C1编译:仅执行带方法调用次数以及循环回边执行次数Profiling的C1编译
  • 3 :完全C1编译:会执行带有所有Profiling的C1代码
  • 4 : C2编译:使用C2编译器进行优化,该级别会启用一些编译耗时较长的优化,一些情况下会根据性能监控信息进行一些非常激进的性能优化
    级别越高,应用启动越慢,优化的开销越高,峰值性能也越高。

分层编译-JVM参数配置示例

  • 只想开启C2 : -XX:-TieredCompilation (禁用中间编译层( 123层) )
  • 只想开启C1 : -XX:+ TieredCompilation -XX:TieredStopAtLevel=1

如何找到热点代码?思路是?

  • 基于采样的热点探测
  • 基于计数器的热点探测

Hotspot内置的两类计数器

  • 方法调用计数器( Invocation Counter )

用于统计方法被调用的次数,在不开启分层编译的情况下,在C1编译器下的默认阈值是1 500次,在C2模式下是10000次。
也可用-XX:CompileThreshold =X指定阈值

  • 回边计数器( Back Edge Counter )
  1. 用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”( Back Edge )。在不开启分层编译的情况下, C1编译器下的默认阈值13995 , C2默认为10700,
    可使用:XX:OnStackReplacePercentage= X指定阈值
  2. 建立回边计数器的主要目的是为了触发OSR ( On StackReplacement )编译,参考文档
  3. 当开启分层编译时, JVM会根据当前待编译的方法数以及编译线程数来动态调整阈值, -XX: CompileThreshold、-XX:
    OnStackReplacePercentage都会失效。

方法调用计数器流程

截屏2022-09-22 下午1.57.06.png

方法调用计数器

如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减,而这段时间就称为此方法统计的半衰周期。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数.XX: -UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间 ,单位是秒

回边计数器流程

截屏2022-09-22 下午2.08.51.png
参数总结
  • -Xmixed :混合模式运行(默认)
  • -Xint :设置JVM的执行模式为解释执行模式
  • -Xcomp :JVM优先以编译模式运行,不能编译的,以解释模式运行。
  • -XX:-TieredCompilation :禁用中间编译层
  • -XX:TieredStopAtLevel :到哪个分层停止
  • -XX:CompileThreshold=X :指定方法调用计数器阈值(关闭分层编译时才有效)
  • -XX:OnStackReplacePercentage=X :指定回边计数器阈值(关闭分层编译时才有效)
  • -XX:-UseCounterDecay :关闭方法调用计数器热度衰减
  • -XX:CounterHalfLifeTime :指定方法调用计数器半衰周期(秒)

编译器优化-方法内联

把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用

方法内联条件
  1. 方法体足够小
  • 热点方法:如果方法体小于325字节会尝试内联,可用-XX:FreqInlineSize修改大小
  • 非热点方法:如果方法体小于35字节,会尝试内联,可用-XX:MaxInlineSize修改大小
  1. 被调用方法运行时的实现被可以唯一确定
  • static方法、private方法及final方法 , JIT可以唯一确定具体的实现代码
  • public的实例方法,指向的实现可能是自身、父类、子类的代码,当且仅当JIT能够唯一确定方法的具体实现时,才有可能完成内联
方法内联注意点
  1. 尽量让方法体小一些
  2. 尽量使用final、private、 static 关键字修饰方法, 避免因为多态,需要对方法做额外的检查
  3. 一些场景下,可通过JVM参数修改阈值,从而让更多方法内联
内联可能带来的问题

CodeCache的溢出,导致JVM退化成解释执行模式

内联相关JVM参数
  • -XX:MaxTrivialSize=n :默认 6,如果方法的字节码少于该值,则直接内联,单位字节
  • -XX:MinInliningThreshold=n :默认 250,如果目标方法的调用次数低于该值,则不去内联
  • -XX:LiveNodeCountInliningCutoff=n :默认 40000,编译过程中最大活动节点数(IR节点)的上限,仅对C2编译器有效
  • -XX:InlineFrequencyCount=n :默认 100,如果方法的调用点(call site)的执行次数超过该值,则触发内联
  • -XX:MaxRecursiveInlineLevel=n :默认 1,递归调用大于这么多次就不内联
  • -XX:+lnlineSynchronizedMethods :默认 开启,是否允许内联同步方法

逃逸分析,标量替换,栈上分配

标量:不能被进一步分解的量
●基础数据类型
●对象引用
聚合量:可以进一步分解的量,栈上分配
分析变量能否逃出它的作用域

逃逸分析
  • 全局变量赋值逃逸

    例:局部变量赋值给静态变量

  • 方法返回值逃逸
  • 实例引用逃逸
  • 线程逃逸

    赋值给类变量或可以在其他线程中访问的实例变量

逃逸状态标记
  1. 全局级别逃逸:一个对象可能从方法或者当前线程中逃逸
  • 对象被作为方法的返回值
  • 对象作为静态字段(static field)或者成员变量( field)
  • 如果重写了某个类的finalize()方法,那么这个类的对象都会被标记为全局逃逸状态并且一定会放在堆内存中
  1. 参数级别逃逸
  • 对象被作为参数传递给一个方法,但是在这个方法之外无法访问/对其他线程不可见
  • 无逃逸:一个对象不会逃逸
标量替换
  1. 通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时, JVM不会创建该对象,而是创建它的成员变量来代替
  2. XX:+EliminateAllocations开启标量替换( JDK 8默认开启)
  • 标量:不能被进一步分解的量
    1. 基础数据类型
    2. 对象引用
  • 聚合量:可以进一步分解的量

例:

Study study = new Study();
study.id = 1;
study.age = 1;

//开启标量替换后
int id = 1;
int age = 1;
栈上分配

通过逃逸分析,能够确认对象不会被外部访问,就在栈上分配对象

相关JVM参数
  • -XX:+DoEscapeAnalysis :默认 开启,是否开启逃逸分析
  • -XX:+ EliminateAllocations :默认 开启,是否开启标量替换
  • -XX:+ EliminateLocks :默认 开启,是否开
  • 启锁消除

垃圾回收

相关知识点
参考基础面试题大全主要知识点复习总结中JVM虚拟机部分
参考JVM知识整理
垃圾收集器相关JVM参数总结
从实际案例聊聊Java应用的GC优化
JVM调优工具锦囊
JVM调优工具及案例分析
性能调优工具
利用工具调优分析问题
JVM调优

JVM调优-内置工具

JDK工具
JDK自带了很多性能监控工具,我们可以用这些工具来监测系统和排查内存性能问题。


内置工具

监控工具

jps

jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令。

相关命令

  • jps -q :只显示进程号
  • jps -m :显示传递给main方法的参数
  • jps -l :显示应用main class的完整包名应用的jar文件完整路径名
  • jps -v :显示传递给jvm的参数
  • jps -V :禁止输出类名、jar文件名和传递给main方法的参数,仅显示本地jvm标识符列表

命令混合使用:
示例:

  • jps -l -v
  • jps -m -l -v
  • jps -ml
  • jps -vl 输出传递给JVM的参数

和jps功能类似的有

  • ps -ef | grep java
  • ps aux | grep java
jstat

使用 jstat 工具可以监测 Java 应用程序的实时运行情况,可以看到VM内的Eden、Survivor、老年代的内存使用情况,还有 YoungGC 和 FullGC 的执行次数以及耗时。通过这些指标,我们可以轻松的分析出当前系统的运行情况,判断当前系统的内存使用压力以及GC压力,还有内存分配是否合理。
查看官网jstat及相关命令
语法

jstat [ generalOption | outputOptions [-t] [-h] vmid [ interval[s|ms] [count] ]

  • interval 单位是毫秒,每过多久打印一次
  • count 打印多少次,默认一个参数是interval
  • -t 程序从开始执行到现在过了多少时间 ms
  • -h 每个多少多少条打印一次表头
generalOption

单个常规命令

  • jstat -help :展示帮助信息
  • jstat -options :展示所有的静态列表选项

相关命令
【jstat -options】
将javaOptions参数传递给Java应用。查看完整Java参数

截屏2022-11-03 上午11.43.56.png

  • -class:显示 ClassLoad 的相关信息;
  • -compiler:显示 JIT 编译的相关信息;
  • -gc:显示和 gc 相关的堆信息;
  • -gccapacity:显示各个代的容量以及使用情况;
  • -gcmetacapacity:显示 Metaspace 的大小;
  • -gcnew:显示新生代信息;
  • -gcnewcapacity:显示新生代大小和使用情况;
  • -gcold:显示老年代和永久代的信息;
  • -gcoldcapacity :显示老年代的大小;
  • -gcutil:显示垃圾收集信息;
  • -gccause:显示垃圾回收的相关信息(同 -gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
  • -printcompilation:输出 JIT 编译的方法信息
    其中 jstat -gc 是最完整、最常用、最实用的命令,基本足够分析jvm的运行情况了。

显示 ClassLoad 的相关信息
【jstat -class

截屏2022-11-03 上午11.46.42.png

  • Loaded : 已加载的类个数
  • Bytes : 已加载的字节数(kb)
  • Unloaded : 未加载的类个数
  • Bytes : 未加载的字节数(kb)
  • Time : Time spent performing class loading and unloading operations

Java HotSpot VM即时编译器
【jstat -compiler [ []】

截屏2022-11-03 上午11.45.09.png

  • Compiled:已执行的编译数
  • Failed :失败的编译数
  • Invalid :无效的编译数
  • Time :执行编译的总时间
  • FailedType :最后一次失败编译的编译类型
  • FailedMethod:最后-次编译失败的类名和方法

查看内存使用和GC情况
【jstat -gc [ []】

截屏2022-11-03 上午11.47.39.png

  • S0C:年轻代中 To Survivor 的容量(单位 KB);
  • S1C:年轻代中 From Survivor 的容量(单位 KB);
  • S0U:年轻代中 To Survivor 目前已使用空间(单位 KB);
  • S1U:年轻代中 From Survivor 目前已使用空间(单位 KB);
  • EC:年轻代中 Eden 的容量(单位 KB);
  • EU:年轻代中 Eden 目前已使用空间(单位 KB);
  • OC:老年代的容量(单位 KB);
  • OU:老年代目前已使用空间(单位 KB);
  • MC:Metaspace 的容量(单位 KB);
  • MU:Metaspace 目前已使用空间(单位 KB);
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:从应用程序启动到采样时年轻代中 gc 次数;
  • YGCT:从应用程序启动到采样时年轻代中 gc 所用时间 (s);
  • FGC:从应用程序启动到采样时 old 代(全 gc)gc 次数;
  • FGCT:从应用程序启动到采样时 old 代(全 gc)gc 所用时间 (s);
  • GCT:从应用程序启动到采样时 gc 用的总时间 (s)

查看垃圾回收统计
【jstat -gcutil [ []】

截屏2022-11-03 下午12.20.06.png

  • S0:Survivor0 区占用百分比
  • S1:Survivor1 区占用百分比
  • E:Eden 区占用百分比
  • O:老年代占用百分比
  • M:元数据区占用百分比
  • YGC:年轻代回收次数
  • YGCT:年轻代回收耗时
  • FGC:老年代回收次数
  • FGCT:老年代回收耗时
  • GCT:GC总耗时

【jstat -gccause
这个参数展示的垃圾收集信息与-gcutil基本一致,多了最后两列


截屏2022-11-03 下午12.21.30.png
  • LGCC:.上一次垃圾收集发生的原因
  • GCC :本次垃圾收集发生的原因

各年代内存池和空间容量
【jstat -gccapacity

截屏2022-11-03 下午12.23.04.png

  • NGCMN: 最小青年代容量(kb)
  • NGCMX: 最大青年代容量(kb)
  • NGC:当前青年代大小(kb)
  • SOC: survivor 0空间容量(kb)
  • S1C : survivor 1空间容量(kb)
  • EC : eden空间容量(kb)
  • OGCMN:最小老年代容量(kb)
  • OGCMX: 最大老年代容量(kb)
  • OGC: 当前老年代容量(kb)
  • 0C: old空间容量(kb)
  • MCMN: 最小metaspace容量(kb)
  • MCMX :最大metaspace容量(kb)
  • MC: metaspace容量(kb)
  • CCSMN: 最小压缩类空间容量(kb)
  • CCSMX: 最大压缩类空间容量(kb)
  • CCSC :压缩类空间容量(kb)
  • YGC :年轻代GC次数
  • FGC: full GC次数

Java HotSpot 虚拟机编译方法统计信息
【jstat -printcompilation

截屏2022-11-03 下午12.26.56.png

  • Compiled :最近的编译方法执行的编译数(Number of compilation tasks performed by the most recently compiled method)
  • Size :最近编译的方法的字节码的字节数( Number of bytes of byte code of the most recently compiled method)
  • Type :最近编译方法的编译类型
  • Method: 最近编译方法的类名和方法。类路径中用/代替., 这个两个字段的格式与
    HotSpot的-XX:+PrintCompilation 参数一致。
outputOtions

输出选项决定了jstat命令输出的内容和格式,必须有一个statOption,后面可以任意追加-h -t -J参数。
输出内容以表格形式展现,第一行表示每列的意思。

  • -h n
    每多少行显示一次列表头,默认是0,表示只在第一行显示一次列表头


    截屏2022-11-03 下午1.40.48.png
  • -t
    在输出内容的第一列显示 timestamp列,表示当前JVM启动的时间


    截屏2022-11-03 下午1.41.20.png

故障排查工具

jinfo

jinfo 是 JDK 自带的命令,可以用来查看正在运行的 java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;
也可以动态的修改正在运行的 JVM 一些参数。
当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息。
【jinfo

  • 用法


    截屏2022-11-03 下午1.42.11.png
  • 参数说明
  1. pid: 对应jvm的进程id
  2. executable core: 产生core dump文件
  3. [server-id@]remote server IP or hostname: 远程的ip或者hostname,server-id标记服务的唯一性id
  • option
  1. no option:输出全部的参数和系统属性
  2. flag name:输出对应名称的参数
  3. flag [+|-]name:开启或者关闭对应名称的参数
  4. flag name=value:设定对应名称的参数
  5. -flags:输出全部的参数
  6. sysprops:输出系统属性
    具体参考
jmap

使用 jmap 可查看堆内存初始化配置信息以及堆内存的使用情况,输出堆内存中的对象信息,包括产生了哪些对象,对象数量多少等。
用法

截屏2022-11-03 下午1.43.19.png

  • executable : 产生核心转储的Java可执行文件
  • core : 打印内存信息的核心文件
  • remote-hostname-or-IP : 远程调试服务器的hostname或者IP地址。查看jsadebug
  • server-id : 远程服务器上有多个调试服务器时,提供的调式服务器唯一ID
  • 通过-J-d64可以指定jmap运行在64位JVM上 : jmap -J-d64 -heap pid

Options

  1. : jmap 没有option参数,jmap命令输出共享对象映射信息。会输出JVM中所有共享对象的起始地址、映射大小、文件的全路径
    截屏2022-11-03 下午1.43.50.png

    -dump:[live,] format=b, file=*filename*
  • 将Java堆信息存入filename指定文件名的hprof二进制文件中;
  • live 是可选操作,指定live表示只保存活跃对象;
  • 使用jhat命令可以读取文件;
$ jmap -dump:live,format=b,file=test 5208
Dumping heap to C:\user\username\Desktop\test ...
Heap dump file created
$ jhat  C:\\Users\\username\\Desktop\\test
Reading from C:\Users\username\Desktop\test...
Dump file created Fri Dec 27 13:20:01 CST 2019
Snapshot read, resolving...
Resolving 502739 objects...
Chasing references, expect 100 dots....................................................................................................
Eliminating duplicate references....................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

访问localhost:7000可以查看文件具体内容

  1. heap : 输出堆垃圾回收的摘要信息,配置信息,各年代的堆使用信息以及 interned Strings的是数量和大小。
  2. histo[:live]
  • 将堆信息以列表(histogram)的形式输出;
  • 输出每个类的信息:对象个数、内存字节数、全路径;
  • 如果指定live,只统计活跃对象信息
  1. clstats
  • 输出Java堆中类加载器的统计信息;
  • 输出每个类加载器的信息:名称,状态,地址,父类加载器,已加载的类的数量和大小;
  • 统计会比较耗费时间;
  1. F
    强制输出
  • 使用jmap -dump或者jmap -histo命令时,如果进程没有响应,可以使用-F强制输出;
  • live模式下不能使用-F强制输出;
    h help
  • -h与-help都是输出帮助信息的指令
  1. -Jflag : 将flag信息传递给运行jamp命令的虚拟机

查看堆内存情况
【jmap -heap
这个命令会打印出堆内存相关的一些参数设置以及各个区域的情况,要查看这些信息一般使用 jstat 命令就足够了。
查看系统运行时对象分布
【jmap -histo[:live] 】 带上 live 则只统计活对象
这个命令会按照各种对象占用内存空间的大小降序排列,把占用内存最多的对象放在最上面。通过这个命令可以简单的了解下当前jvm中的对象对内存占用的情况以及当前内存里到底是哪个对象占用了大量的内存空间。
生成堆内存转储快照
【jmap -dump:format=b,file=
【jmap -dump:live,format=b,file=
jmap -dump 是输出堆中所有对象;
jmap -dump:live 是输出堆中所有活着的对象,而且 jmap -dump:live 会触发 FullGC,线上使用要注意。format=b 是以二进制格式输出;file 是文件路径,格式为 hrpof 后缀。
这个命令会在当前目录下生成一个 dump.hrpof 文件,这是个二进制的格式,无法直接打开,可以使用MAT等工具来分析。这个命令把这一时刻VM堆内存里所有对象的快照放到文件里去了,供你后续去分析。

jstack

jstack 是一种线程堆栈分析工具,最常用的功能就是使用 jstack pid 命令查看线程的堆栈信息,通常会结合 top -Hp pid 或 pidstat -p pid -t 一起查看具体线程的状态,也经常用来排查一些死锁的异常、CPU占用高的线程等。
jstack参数

  • -l:长列表. 打印关于锁的附加信息,例如属于 java.util.concurrent 的 ownable synchronizers 列表。
  • -F:当 jstack [-l] pid 没有响应的时候强制打印栈信息
  • -m:打印 java 和 native c/c++ 框架的所有栈信息.
  • -h | -help:打印帮助信息

查看线程堆栈信息
【jstack > stack.log】
这个命令可以把程序的线程堆栈dump下来。每个线程堆栈的信息中,都可以查看到线程 ID、线程状态(wait、sleep、running 等状态)以及是否持有锁等。

  • pool-11-thread-6:线程名称
  • 1920:线程编号

  • prio=5:线程的优先级别
  • os_prio=0:系统级别的线程优先级
  • tid=0x00007f87e028c000:线程ID
  • nid=0x6724:native线程的id,通过 printf "%x\n" 命令转换线程ID
  • waiting on condition [0x00007f87b97d2000]:线程当前的状态
jcmd

jhat
分析Java堆内存信息。
语法
【jhat [ options ] heap-dump-file】
参考
jhsdb

可视化工具

jhsdb
jconsole
VisualVM
Java Mission Control

其余参考
JVM调优工具锦囊
JVM调优工具及案例分析
性能调优工具
利用工具调优分析问题
JVM调优

你可能感兴趣的:(JVM性能调优)