jvm(三)——性能监控

一、面试题

 

 

二、背景、概述

1、为什么要进行调优?
——防止、解决OOM;减少Full gc出现的频率

2、生产环境中的问题

1)、生产环境发生了内存溢出该如何处理

2)、生产环境应该给服务器分配多少内存合适?

3)、如何对垃圾回收器的性能进行调优?

4)、生产环境CPU负载飙高该如何处理?

5)、生产环境应该给应用分配多少线程合适?

6)、不加log,如何确定请求是否执行了某一行代码?

7)、不加log,如何实时查看某个方法的入参与返回值?

3、可以根据什么方式对jvm进行监控?

1)、运行日志

2)、异常堆栈

3)、GC日志

4)、线程快照

5)、堆转储快照

可以根据上面的这些方向做出一些调优,例如合理的编写代码、合理使用硬件内存、合理进行jvm调优

三、调优的具体步骤

1、发现问题(性能监控)

通过性能监控可以发现程序中GC频繁、CPU load过高、OOM、内存泄露、死锁、程序响应时间过长等问题。

2、排除/定位问题(性能分析)

由上面的监控可以发现程序的表面问题,第二步我们就需要分析这些问题是怎么造成的,这样才能方便我们下一步去解决问题。

我们可以通过下面的方式去分析问题:

1)、打印GC日志,然后通过GCviewer或者http://gceasy.io来分析日志中存在的异常信息。

2)、运用命令行工具、jstack、jmap、jinfo等分析问题

3)、dump出堆文件,使用内存分析工具分析文件

4)、使用Arthas、jconsole、JVisualVM来实时查看JVM状态

5)、使用jstack查看堆栈信息

3、解决问题

到这里我们已经定位到程序发生异常的具体原因,那么我们最后需要做的就是如何去解决这些问题。

可以通过下面的这些方式去解决问题:
1)、适当增加内存、根据业务背景去选择更加合适的垃圾回收器

2)、优化代码控制内存使用

3)、增加机器、分散节点压力

4)、合理设置线程池线程数量

5)、使用中间件提高程序效率

6)、其他.........

四、性能评价指标

1、响应时间/停顿时间

2、吞吐量

3、并发数

4、内存占用

5、相互间的关系

五、JVM监控及诊断工具(命令行方式)

一、概述

二、jps

查看正在运行的java进程。

基本语法: jps 【options】 【hostid】

options参数:


综合使用:
jps -l -m等价于jps -lm
如何将信息输出到同级文件中:
语法:命令 > 文件名称
例如:jps -l > a.txt

 例如:

hostid参数:

  可以在jdk安装目录下的bin目录看到jps:

 三、jstat(查看jvm运行时各种状态信息,一般用来查看运行时GC的信息)

 基本语法:


其中vmid是进程id号,也就是jps之后看到的前面的号码,如下:

 

 各个参数说明:

1、option(主要参数)

 

 1)、-class举例:jstat -class -t -h3 13152 1000 10,其中h3中的3代表每隔3个分隔一次,13152代表类的进程id,1000代表每隔1000毫秒打印一次,10代表一共打印10次,如下所示:

 其中Timestamp代表程序至今的运行时间,单位为秒;Loaded代表加载的类的数目;Bytes代表加载的类的总字节数;Unloaded代表卸载的类的数目;Bytes代表卸载的类的总字节数;Time代表类装载所消耗的时间; 
2)、-gc举例:jstat -gc 13152,其中13152代表类的进程id,执行结果如下所示:

 其中S0C代表幸存者0区的总容量,S1C代表幸存者1区的总容量,S0U代表幸存者0区使用的容量,S1U代表幸存者1区使用的容量,EC代表伊甸园区的总容量,EU代表伊甸园区使用的总容量,OC代表老年代的总容量,OU代表老年代已经使用的容量,MC代表方法区的总容量,MU代表方法区的总容量,CCSC代表压缩类的总容量,CCSU代表压缩类使用的容量,YGC代表年轻代垃圾回收的次数,YGCT年轻代进行垃圾回收需要的时间,FGC代表代表Full GC的次数,FGCT代表Full GC的时间,GCT代表垃圾回收的总时间
3)、-gccapacity举例:jstat -gccapacity 13152,其中13152代表类的进程id,执行结果如下:

 其中S0C代表幸存者0区的容量,S1C代表幸存者1区的容量,EC代表伊甸园区的容量,CCSC代表压缩类的容量,YGC代表年轻代垃圾回收的时间,FGC代表Full GC垃圾回收的时间
 4)、-gcutil举例:jstat -gcutil 13152,其中13152代表类的进程id,执行结果如下所示:

  以上是各区域占比以及垃圾回收的情况,S0代表幸存者0区,S1代表幸存者1区,E代表伊甸园区,O代表老年代,M代表方法区,CCS代表压缩类,以上这些值都是占比情况,YGC代表年轻代垃圾回收的次数,YGCT年轻代进行垃圾回收需要的时间,FGC代表代表Full GC的次数,FGCT代表Full GC的时间,GCT代表垃圾回收的总时间
 5)、-gccause举例:jstat -gccause 13152,其中13152代表类的进程id,执行结果如下:

 以上是各区域占比以及垃圾回收的情况,还有触发垃圾回收的原因解释,S0代表幸存者0区,S1代表幸存者1区,E代表伊甸园区,O代表老年代,M代表方法区,CCS代表压缩类,以上这些值都是占比情况,YGC代表年轻代垃圾回收的次数,YGCT年轻代进行垃圾回收需要的时间,FGC代表代表Full GC的次数,FGCT代表Full GC的时间,GCT代表垃圾回收的总时间,LGCC和GCC代表垃圾回收的原因
2、interval参数

用于指定输出统计数据的周期,单位为毫秒。即:查询间隔

3、count参数

用于指定查询的总次数

4、-t参数

可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒

 我们执行jstat -gc -t 13152 1000 10,这代表1秒打印出1行,一共10行,-t代表打印出Timestamp总运行时间,结果如下所示:

 上方红色框框中代表Timestamp,而蓝色框框中代表垃圾回收时间,单位都是秒,如果让红色框框中的某两个值相减,假设这个值是num1,然后让对应行的蓝色框框中的另外两个值相减,假设这个值是num2,之后让num2/num1,得出的差值就是上述所说的GC时间占运行时间的比例
虽然这种方式比较繁琐,但是在项目部署之后就需要使用命令行去看了,就没有可视化界面了,所以这种方式也要会
5、-h参数

可以在周期性数据输出时,输出多少行数据后输出一个表头信息

补充:

 第1步可以执行命令:jstat -gc -t 13152 1000 10

四、jinfo(实时查看和修改jvm配置参数)

 

 基本语法:

 1、利用jinfo查看参数

可以先使用jps查看进程id

 1)、jinfo -sysprops 进程id  (可以查看由System.getProperties()取得的参数)

2)、jinfo -flags 进程id (查看曾经赋值过的参数)

有一些参数是我们自己赋值的,一些是系统自动优化设置的参数信息。

 3)、jinfo -flag 参数名 进程id (查看某个参数的值)

 2、利用jinfo修改参数

 

 1)、针对boolean类型

jinfo -flag [+|-]参数名称 进程id

如果使用+号,那就可以让该参数起作用,否则使用-号就让该参数不起作用,具体例子如下:

 2)、针对非boolean类型

jinfo -flag 参数名称=参数值 进程id


扩展:
java -XX:+PrintFlagsInitial   (查看所有JVM参数启动的初始值)

java -XX:+PrintFlagsFinal    (查看所有JVM参数的最终值)

 值前面添加冒号:的是修改之后的值,没有添加的都是没有发生改变的初始值

java -参数名称:+PrintCommandLineFlags   (查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值)

五、jmap(导出内存映像文件/内存使用情况)

 基本语法:

 使用语法可以通过在DOS窗口中使用jmap/jmap -h/jmap -help查看,<executable <core>代表可执行的代码,比如使用> 文件名称来指定生成的dump文件的生成位置,[server_id@]<……>是为远程连接准备的

1、参数:

 2、使用jmap导出内存映像文件

 

 1)、手动导出

jmap -dump:format=b,file=<filename.hprof> <pid>

或者

jmap -dump:live,format=b,file=<filename.hprof> <pid>

 两者的差别在于多了个live。live表示只生成在堆中存活对象的快照。没加live的是所有对象。

 说明:
<filename.hprof>中的filename是文件名称,而.hprof是后缀名,<***>代表该值可以省略<>,当然后面的<pid>是进程id,需要通过jps查询出来,format=b表示生成的是标准的dump文件,用来进行格式限定。
 
具体例子如下:
生成堆中所有对象的快照:

 

 其中file=后面的是生成的dump文件地址,最后的11696是进程id,可以通过jps查看
 
一般使用的是第二种方式,也就是生成堆中存活对象的快照,毕竟这种方式生成的dump文件更小,我们传输处理都更方便

2)、自动导出

 具体如下:

 dump出快照文件后则可以利用工具进行分析

3、使用jmap打印堆内存信息

上面使用jmap导出整个内存的快照,如果我们只需要导出堆内存的信息。

1)、jmap -heap 进程id

 2)、jmap -histo 进程id

 4、使用jmap打印ClassLoader信息

jmap -permstat 进程id   (查看系统的ClassLoader信息)

5、使用jmap打印堆积在finalizer队列中的对象

jmap -finalizerinfo   (查看堆积在finalizer队列中的对象)

其中这两个指令(jmap -permstat 进程id、jmap -finalizerinfo)仅linux/solaris平台有效,所以无法在windows操作平台上演示,并且使用比较小众,不在多说。

 

六、jhat(JDK自带的堆分析工具)

jhat命令在jdk9及其之后就被移除了,官方建议使用jvisualvm代替jhat,所以该指令只需简单了解一下即可。

 基本语法:

 七、jstack(打印JVM中线程快照)

 基本语法

 

 option参数:

 这样可以获取到线程快照、锁等信息用于分析问题。

八、jcmd(多功能命令行)

 jcmd -l   (列出所有的JVM进程,类似jps命令)

jcmd 进程号 help   (针对指定的进程,列出支持的所有具体命令)

 

 jcmd 进程号 具体命令  (显示指定进程的指令命令的数据)

 

 九、jstatd(远程主机信息收集)


 上面的几个命令,可以着重去看jps、jstat、jinfo、jmap、jstack。虽然后面会有一些GUI工具可以代替这些,不过项目上线后如果在没有专门做监控模块的话,就需要这些命令去分析线上问题。

(这里做个参考,jmap获取的是进程的内存信息,而jstack获取的是线程快照,有区别?如果一个进程中有多个线程,那打印的是哪个线程?还是所有都打印?如何分析dump文件(jmap)、线程快照(jstack)、gc信息(jstat)。

六、JVM监控及诊断工具(GUI方式)

一、概述

 二、JConsole

 在jdk安装目录的console.exe,双击即可执行文件。或者打开cmd窗口,输入jconsole命令即可。

此时则 可以进行本地或者远程的进程监控。此时我们选择某个本地进程。

此时可以查看这个jvm进程的内存、线程、类、vm概要等信息,

 可以类似jmap、jstat命令的一些信息:

 

 下面的线程可以用来检测死锁等(jstack命令)

 

 

 使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接起链接到平台的MBean的服务器上。这种从本地连接的监控能力只有Sun的JDK具有。

远程连接(Remote):使用下面的URL通过RMI连接器连接到一个JMX代理,service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码,从而进行授权。

Advanced:使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用


三、Visual VM

jdk安装目录的visualvm.exe双击,或者cmd窗口执行jvisualvm即可,如下图: 

可以查看一些内存信息、dump出内存快照:

dump出来的内存快照:(dump出来的快照可以右键保存)

 可以查看线程信息、dump出线程快照:

dump出来的线程快照:

(可以观察是否有死锁、阻塞等现象) 

可以抽样检测cpu、内存等状态

也可以导入dump文件:

 

远程连接:

Visual VM的主要作用:

Visual VM的插件安装:


四、Eclipse MAT

集成在Eclipse的性能分析插件,由于现在基本使用idea所以不做介绍

五、JProfiler

idae中的一个收费插件,这个也不做介绍

六、Java Misssion Control

 

 作用:用来实时监控JVM运行时的状态


 Java Flight Recorder

1、概述

 2、事件类型

 3、启动方式:

1)、方式一

2)、方式二

 3)、方式三

 具体使用这个插件:

 

 

 

 利用JFR进行取样分析:

 

 

 

 测试代码:

 取样结果:

1、一般信息

2、内存

3、代码

4、线程

5、I/O

6、系统

7、事件

 七、其他工具

八、Arthas

阿里开源的性能功能。前面说过的jvisualvm、jprofiler等要进行远程连接需要在服务端项目开启时配置很多参数,而且如果线上环境是隔离的,那么本地工具是连接不到线上环境的。还有Jprofiler这款工具是收费的。

Arthas是阿里开源的性能监控神器。阿尔萨斯在远程连接时不需要进行复杂的参数配置,可以在线排除问题,无需重启应用,可以动态跟踪java代码,实时监控JVM状态。

使用的是命令行交互的模式。

 

 官方使用文档:快速入门 — Arthas 3.5.4 文档

这个后面会写一个详细的操作文章。这里就不多说。

七、JVM运行时参数

对于jvm参数可以看官网说明:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

一、相关的JVM参数

1、类型一:标志参数

这类参数比较稳定,一般不会改变,以-开头,可以使用java -help查看所有标准参数。

 

 

 对于以上第2点,我们可以打开DOS窗口,输入java -version就可以看到64位机器上用的server模式,如下所示:

 2、类型二:-X参数

非标准参数,算比较稳定,但后面也可能改变,以-X开头。

java -X可以查看所有-X参数:

 JIT的编译模式也是-X参数,该编译模式有三种:-Xint、-Xcomp、-Xmixed,默认是第三种混合模式(混合模式:这是默认模式,刚开始的时候使用解释器慢慢解释执行,后来让JIT即时编译器根据程序运行的情况,有选择地将某些热点代码提前编译并缓存在本地,在执行的时候效率就非常高了)

例如我们查看jdk版本时就是有说明:


注意:-Xmx -Xms -Xss的作用等价于某些XX参数(单位:k/K、m/M、g/G,设置:-Xmx、-Xms最好设置成一样的值,避免扩容带来的损耗)。

-Xms<size>       设置初始Java堆大小,等价于-XX:InitialHeapSize

查看该参数值的时候,应该使用InitialHeapSize,例如jinfo flag InitialHeapSize 进程id
等价证明:

 -Xmx<size>       设置最大Java堆大小,等价于-XX:MaxHeapSize

 -Xss<size>         设置Java线程堆栈大小,等价于-XX:ThreadStackSize

 3、类型三:-XX参数(常用参数)

非标准化参数、常用、以-XX开头。主要用于开发和调试JVM。

这类参数又可以分为两种格式:
1)、Boolean类型:

-XX  +<option>   表示启动某个option属性;

-XX  -<option>   表示禁用某个option属性;(说明:因为有的指令默认是开启的,所以可以使用-关闭)

例如:

 2)、非Boolean类型(key-value):

2-1)、数值型格式: -XX <option> = <number>

 2-2)、非数值型格式:-XX <name> = <string>.

 补充:-XX:+PrintFlagsFinal的作用

 二、添加jvm参数选项

1、通过idea进行设置参数:

 2、通过运行jar包命令设置参数:

例如:java -Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar demo.jar

3、程序运行时,可以设置一部分参数:


  三、常用的jvm参数选项

1、打印查看参数

 其中第三个:

 2、堆、栈、方法区内存大小设置参数(常用)

2-1)、堆的参数

 对于其中一些参数的建议:

 其实一般推荐调整的是-Xms、-Xmx、-Xmn这几个参数(其中一般将-Xms和-Xmx的大小设置为一样,防止-Xms扩容时的性能消耗)

2-2)、栈的参数(这个也常用)

-Xss128k    等价于-XX:ThreadStackSize,设置每个线程的栈大小为128k

2-3)、方法区的参数

方法区需要看jdk版本,有的是永久代,有的是元空间。

jdk1.8的方法区使用的是堆外内存(元空间直接使用本地内存,所以默认是无限制) 

2-4)、直接内存

-XX:MaxDirectMemorySize   指定DirectMemory容量,若未指定,则默认与Java堆最大值一样。

元空间使用的是直接内存?什么是直接内存?(直接内存和元空间没关系,元空间是使用本地内存)

请问 -XX:MaxDirectMemorySize= 限制的是 元空间大小 还是 NIO 占用大小? - 知乎

3、OOM相关的参数

 4、垃圾收集器相关的参数

 

 1)、查看默认的垃圾回收器

 2)、Serial回收器

 3)、Parnew回收器

 4)、Parallel回收器

 5)、CMS回收器

 

 6)、G1回收器

 如果使用G1垃圾收集器,不建议设置-Xmn和-XX:NewRatio,毕竟可能影响G1的自动调节。

Mixed GC调优参数:

 7)、怎么选择垃圾回收器?
 5、GC日志相关的参数(性能分析中常用)

1)、常用参数

 对于这些参数:

 

 

 

 

 

 

 这些参数在分析调优过程常用。

2)、其他参数

 

6、其他常用的参数

 四、通过java代码获取jvm参数

 代码:(Runtime对象:运行时对象)

八、分析GC日志

一、GC日志参数

上面已经说过GC相关的常用参数:

 二、GC日志格式

1、GC分类

 

 2、不同GC分类的GC细节

例如老年代使用CMS:

 例如新生代使用serial GC:

 3、GC日志分类

GC日志主要分为MinorGC(Young GC)日志、FullGC

 

 4、GC日志结构

观察打印出来的GC日志时,一般有几个信息可以观察

1)、垃圾收集器

2)、GC前后内存等的变化

 3)、 GC消耗时间

 5、Minor GC日志解析

我们这里对于一个简单的Minor GC日志进行分析,我们得到下面的GC信息

 1)、2020-11-20T17:19:43.265-0800   

日志打印时间,日期格式2013-05-04T21:53:59.234+0800。

添加-XX:+PrintGCDateStamps参数可以打印

2)、0.822:

gc发生时,java虚拟机启动以来经过的秒数。

添加-XX:+PrintGCTimeStamps该参数可打印。

3)、[GC(Allocation Failure)

发生了一次垃圾回收,这是一次Minior GC。它不区分新生代还是老年代GC,括号里的内容是gc发生的原因,这里的Allocation Failure的原因是新生代中没有足够区域能够存放需要分配的数据而失败,所以触发gc。

4)、[PSYoungGen:76800K->8433K(89600K)]

其中PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的。例如

 76800K->8433K(89600K):GC前该内存区域已使用容量->GC后盖区域容量(该区域总容量),如果是新生代,总容量则会显示整个新生代内存的9/10,即eden+from/to区;如果是老年代,总容量则是全身内存大小,无变化。

5)、76800K->8449K(294400K)

堆总内存、已使用内存变化信息的打印,虽然本次是Minor GC,只会进行新生代的垃圾收集,但是也肯定会打印堆中总容量相关信息。

在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量->GC后堆内存容量(堆内存总容量),并且堆内存总容量 = 9/10 新生代 + 老年代,然后堆内存总容量肯定小于初始化的内存大小。

6)、,0.0088371

整个GC所花费的时间,单位是秒

7)、[Times:user=0.02 sys=0.01,real=0.01 secs]

 6、Full GC日志解析

以下面的Full GC日志例子进行分析

 1)、2020-11-20T17:19:43.794-0800

和上面类似,日志打印时间

2)、1.351

和上面类似

3)、Full GC(Metadata GCThreshold)

括号中是gc发生的原因,原因:Metaspace区不够用了。除此之外,还有另外两种情况会引起Full GC,如下:
1、Full GC(FErgonomics)     原因:JVM自适应调整导致的GC
2、Full GC(System)      原因:调用了System.gc()方法

4)、[PSYoungGen: 100082K->0K(89600K)]

PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的。

10082K->0K(89600K):GC前该内存区域已使用容量->GC该区域容量(该区域总容量),如果是新生代,总容量则会显示整个新生代内存的9/10,即eden+from/to区;如果是老年代,总容量则是全身内存大小,无变化。(这部分是Young GC)

5)、ParOldGen:32K->9638K(204800K)

老年代区域没有发生GC,因此本次GC是metaspace引起的。(老年代内存使用在GC后从少到多,是年轻代的对象过来了,而且也可以说明没有发生老年代的gc)

6)、10114K->9638K(294400K)

在显示完区域容量GC的情况之后(内存使用在GC前后变化),会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量->GC后堆内存容量(堆内存总容量),并且堆内存总容量 = 9/10 新生代 + 老年代,然后堆内存总容量肯定小于初始化的内存大小

7)、[Meatspace:20158K->20156K(1067008K)]

metaspace GC 回收2K空间。其他的信息和上面的类似。

三、GC日志分析工具

 1、GCEasy

 2、GCViewer

 3、其他工具

1)、GChisto

 官网上没有下载的地方,需要自己从SVN上拉下来编译,不过这个工具似乎没怎么维护了,存在不少bug。

2)、HPjmeter

工具很强大,但是只能打开由以下参数生成的GC log,-verbose:gc -Xloggc:gc.log。添加其他参数生成的gc.log无法打开。HPjmeter集成了以前的HPjtune功能,可以分析在HP机器上产生的垃圾回收日志文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值