示例:例如在程序运行时间T内,发生N次GC,接口响应平均时间为50ms,GC平均时间为25毫秒,那么受GC影响请求占比 = (50 + 25) * N / T
使用jstat -gc -t 命令,找到Timestamp、YGC、YGCT、FGC、FGCT列
受YGC影响请求占比 = (接口响应平均时间(ms) + YGCT / YGC * 1000) * YGC / (Timestamp * 1000)
受FGC影响请求占比 = (接口响应平均时间(ms) + FGCT / YGC * 1000) * FGC / (Timestamp * 1000)
JDK的很多小工具的名字都参考了UNIX命令的命名方式
options参数:
hostid参数:查看远程主机上的Java进程,一般不使用。一般都是使用Xshell直接连接至远程服务器,实际使用jps -lmv命令就可以了
有些时候我们希望查看Java进程的某些JVM参数,或者实时修改某些JVM参数,那么jinfo命令可以帮我们做到这点
修改
jstat是JDK自带的一个轻量级小工具。全称Java Virtual Machine statistics monitoring tool,jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id和所选参数
帮助信息
基本格式 jstat - [-t] [-h] [] []
class 显示classLoader相关信息
compiler 显示JIT编译相关信息
gc 显示与GC相关堆信息
gccapacity 显示各个代的容量以及使用情况
gccause 显示垃圾收集相关信息,最后一次垃圾回收的原因
gcnew 显示新生代信息
gcnewcapacity 显示新生代使用情况
gcold 显示老年代和永久的信息
gcoldcapacity 显示老年代的大小
gcpermcapactiy 显示永久代的大小
gcutil 显示垃圾收集信息
printcompilation 输出JIT编译的方法信息
interval 可选参数,指定输出统计数据的周期,单位毫秒,即为查询间隔
count 可选参数,指定查询的总次数
-t 可以在输出信息之前加上一个timestamp列,显示程序运行的时间,单位秒
-h 可以在周期性输出数据的同时,在输出多少行之后输出一个表头信息
这里可以计算一下吞吐量以及GC停顿时间
示例2:jstat -gccause pid 显示伊甸园区、幸存者区、老年代、元空间使用比例,以及上一次GC发生的原因
jmap全称(JVM Memory Map):作用一方面获取dump文件(堆转储快照,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆内存各区域的使用情况、堆中对象的统计信息、类加载信息等
基本格式:jmap [option] pid
option参数
示例1:导出堆转储快照文件
示例3:查看存活对象情况(jmap -histo:live )
可以通过该命令可以判断对象生成的速率,例如前后间隔5秒,打印出对象直方图。查看所属类实例的增长情况
jmap相关的命令不能在生产环境随便使用,尤其是dump内存快照这个命令,当想要获取JVM运行时的内存快照时,需要满足让所有的用户线程都停止在安全点或者是安全区,停止所有的用户线程(STW)这样对象间的引用关系不会发生变化,得到的dump结果才是正确的
jstack命令可以dump当前运行JVM的线程快照,线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
示例:dump某个Java进程的线程快照,并且输出锁的附加信息
Java进程中包含JVM线程和用户线程,
JVM线程:
打印内容:
Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱, Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
官方文档:
主要应用的场景:
HelloController的代码,访问http://localhost:8080/test,控制台打印不同级别的日志信息
logback-spring.xml的配置文件,默认为info级别
控制台输出info级别及以上的日志信息
接下动态修改HelloControlller的日志级别为debug,并进行访问测试,使用sc -d 全类名命令,查看加载该类的类加载器的hashcode值
使用logger -n 全类名命令,查看待修改类的日志级别
使用logger -c 类加载器的hash值 -n 全类名 -l debug命令,修改日志级别
使用curl命令进行访问测试
控制台输出debug及以上级别的日志
使用logger -c 类加载器的hash值 -n 全类名 -l info命令重新修改为info级别
sysprop——查看和修改JVM的系统属性
jinfo -sysprops 命令类似,查看JVM系统属性,
修改JVM某个系统属性,将user.country从CN修改为US,修改完成之后查看
sysenv——查看JVM的环境变量,查看当前JVM的环境属性(System Environment Variables)
vmoption——查看和修改JVM里诊断相关的option
vmoption基本上都是可以通过jinfo查看以及动态修改的JVM参数,查看所有参数:vmoption
查看指定参数的值:vmoption PrintGCDetails
修改指定参数的值:vmoption HeapDumpOnOutOfMemoryError true
jad——反编译指定已加载类的源码,这个指令非常有用,可以将类进行反编译,jad命令将类反编译后就可以查看源码,与本地代码进行比对
反编译java.lang.String类的toString()方法,并且重定向到String.java中,命令jad --source-only java.lang.String
sc——查看JVM已加载的类信息
sc是search class的简写,顾名思义查看类的详细信息,该命令主要是查看加载该类的类加载器的hash值
查看java.lang.String的信息
mc——内存编译器,内存编译.java文件为.class文件
编译生成.class文件之后,可以结合retransform命令实现热更新代码,注意,mc命令有可能失败。如果编译失败可以在本地编译好.class文件,再上传到服务器
先使用jad命令进行反编译,然后使用vim命令,修改源码,添加了一条测试输出
使用mc -c 类加载器的hash值 源码文件名命令,进行内存编译
retransform——加载外部的.class文件,retransform到JVM里
加载指定的.class 文件,然后解析出class name,再使用retransform命令替换JVM中已加载的对应的类,每加载一个.class 文件,则会记录一个retransform entry,不同的是这个只是临时替换,还有机会可以反悔,也就是删除retransform entry即可
示例:修改HelloController中的代码进行测试
使用curl命令进行测试
消除retransform的影响,只需要删除对应的entry即可
重新使用curl命令进行测试,发现测试日志没有了
retransform的限制:不允许新增、删除field、method,不允许修改field名,不允许修改方法参数、方法名称及返回值,只能修改方法体中的内容,正在跑的函数,没有退出不能生效
上传.class文件到服务器的技巧:有的服务器不允许直接上传文件,可以使用base64命令来绕过,在本地先转换.class文件为base64,再保存为result.txt,即base64 < Test.class > result.txt,到服务器上,新建并编辑result.txt,复制本地的内容,粘贴再保存,把服务器上的result.txt还原为.class,base64 -d < result.txt > Test.class
redefine——加载外部的.class文件,redefine到JVM里
可以实现代码的热更新,redefine的class不能修改、添加、删除类的field和method,包括方法参数、方法名称及返回值,和retransform命令类似,区别在于redefine后的原来的类不能恢复,retransform可以通过删除entry恢复
sm——查看已加载类的方法信息
Search-Method的简写,这个命令能搜索出所有已经加载了Class信息的方法信息,查看java.lang.String的toString方法
classloader——查看classloader的继承树、urls、类加载信息
按类加载实例查看统计信息
可以查看某个类加载器实际的urls,这里的urls指类加载器加载的路径,也就是说会把该路径下的所有类加载到内存中,可以让指定的classloader去getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException比较有用,查看URLClassLoader实际的urls
使用ClassLoader去查找resource
使用ClassLoader去查找class文件
观察指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写OGNL表达式进行对应变量的查看
命令格式watch [-option] class-pattern method-pattern [express] [condition-express]
命令解释:观察类的某个方法的执行情况,包括入参、返回值(express表达式:“{params,returnObj}”)等。有时候观察所有情况无意义,可以在特定条件下触发,例如运行时间大于100ms(condition-express:‘#cost>200’)
express:观察表达式,观察表达式的构成主要由ognl表达式组成,可以写"{params,returnObj}",只要是一个合法的ognl表达式,都能被正常支持
condition-express:条件表达式,当条件表达式为true时,才进行输出,例如发生异常才输出(-e选项),例如当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用(#cost>200(单位是ms)),例如第一个参数小于0时才输出(“params[0]<0”)
特别说明,watch命令定义了4个观察事件点,即-b(before)方法调用前,-e(exception)方法异常后,-s(success)方法返回后,-f(e + s)方法结束后,4个观察事件点-b、-e、-s默认关闭,-f默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出,这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了-b事件点params代表方法入参外,其余事件都代表方法出参,当使用-b时,由于观察事件点是在方法调用前,此时返回值或异常均不存在
案例:
tt -i 1024 -p --replay-times 1
你会发现结果虽然一样,但调用的路径发生了变化,由原来的程序发起变成了Arthas自己的内部线程发起的调用了。
tt --delete-all,删除所有的方法调用记录
以-开头,比较稳定,后续版本基本不会发生变化
例如-D<名称>=<值>,设置环境变量的值,Java代码可以通过System.getenv(名称)方法获取设置的值
运行java或者java -help可以看到所有的标准选项
非标准化参数,功能比较稳定,后续版本可能发生变化,以-X开头
运行java -X命令可以看到所有的X选项
JVM的JIT编译模式相关的选项
注意:
非标准参数,使用最多的参数类型,这类选项属于实验性,不稳定,以-XX开头,主要作用是用于开发和调试JVM
-XX:+PrintFlagsFinal可以输出所有参数的名称和默认值
-Xss128k,设置每个线程的栈大小为128k,等价于-XX:ThreadStackSize=128k
-XX:MaxDirectMemorySize,设置直接内存大小,未指定默认和Java堆内存的最大值一样
java -XX:+PrintCommandLineFlags -version
JAVA8默认使用Parallel并行垃圾收集器,使用jinfo -flag 垃圾回收器选项命令,查看是否启用对应的垃圾收集器
Serial收集器作为HotSpot中Client模式下默认的新生代垃圾收集器,
-XX:+UseSerialGC,启用Serial收集器,开启后,新生代和老年代默认都启用串行也就是Serial和Serial Old收集器
没有最好的收集器,更没有万能的收集器,调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器
-verbose:gc,输出GC日志信息,默认输出到标准输出中,可以独立使用
-XX:+PrintGC,等同于-verbose:gc,可以独立使用
-XX:+PrintGCDetails,在发生垃圾回收时打印内存回收详细信息,并且在进程退出时输出内存各区域的使用情况,可以独立使用
-XX:+PrintGCTimeStamps,输出GC发生的时间戳。
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC,每一次GC前和GC后,都打印信息,可以独立使用
-Xloggc:,把GC日志写入到一个文件中,而不是打印到标准输出中
-XX:+UseGCLogFileRotation,启动GC日志进行滚动
-XX:NumberOfGClogFiles=1,GC日志文件的循环数目
-XX:GCLogFileSize=1M,当GC日志文件达到多大时,进行日志滚动
如果需要对GC日志进行滚动切割,可以使用logrotate切割GC日志
针对HotSpot VM的实现,它里面的GC按照回收区域分为两大种类型:一种部分收集(Partial GC),一种整堆收集(Full GC)
部分收集
整堆收集(Full GC),收集整个Java堆和方法区
CMS
G1的GC
ZGC的GC
GCEasy,通过GC日志可视化分析工具,我们可以很方便的看到JVM各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,将GC日志上传到GCEasy中,点击分析即可。
内存泄漏是指程序申请并使用内存后一直不释放,内存一直占用着。一次泄露不会有明显影响,累积泄露会导致OOM(内存溢出),内存泄漏是导致内存溢出的原因之一,如果应用周期性发布,重新部署,内存泄漏可能就非常隐蔽,难以发现,内存泄漏是由于代码不善引起的,要注意程序一些容易犯错的地方。
内存泄漏的典型现象:
图示是一个博客关于内存泄漏的案例,可以看到老年代内存占用呈现逐步上升趋势,且每次Old GC后,内存占用仍然呈现上升趋势
内存泄漏案例
典型内存泄漏用法:
为什么使用String类作为key,就没有发生内存泄漏呢?
因为String类手动重写了hashCode()和equals()方法,并且String类是不可变的,不能进行修改,只会返回一个新的String对象。
show-busy-java-threads,显示繁忙的java线程脚本,用于快速排查Java的CPU性能问题(top us值过高),自动查出运行的Java进程中消耗CPU多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。
show-busy-java-threads
arthas的thread -n 线程数命令
案例分析
直接原因,内存不够用了,JVM频繁调用垃圾收集器回收内存但是回收的内存较少或者根本就没有回收内存,导致Full GC频繁执行,Full GC可能会发生OOM,也可能不会发生OOM
一般发生事故需要先保护现场然后排查问题