一、面试题
二、背景、概述
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机器上产生的垃圾回收日志文件。