arthas中文直译过来叫阿尔萨斯,是阿里巴巴开源的java应用诊断工具,在线排查问题,无需重启服务,实时监控jvm状态。支持JDK6以上版本,支持Linux/Mac/Window。
采用命令行交换方式,tab键命令自动补全,而且兼容linux系统部分命令,如:grep,ctrl+l清屏,ctrl+a跳到行首,ctrl+c终止命令等
如果你还在使用jdk原生jstat、jmap等命令排查问题,那么,强烈建议你使用arthas。
一、arthas调试线上环境bug(简直不要太优雅)
下载安装
arthas安装分为快速安装和全量安装,快速安装只下载arthas-boot.jar。全量安装是下载一个压缩包,压缩包中包含arthas-boot.jar、测试demo和封装好的启动脚本等文件,一般我们使用快速安装就可以了
快速安装
linux系统安装可以用curl命令从阿里云官方网站下载
curl -O https://arthas.aliyun.com/arthas-boot.jar
windows系统安装用浏览器打开如下地址会提示下载
https://arthas.aliyun.com/arthas-boot.jar
全量安装
linux全量安装需要先下载执行脚本
curl -L https://arthas.aliyun.com/install.sh | sh
下载完成后会在当前目录生成as.sh脚本,执行脚本 ./as.sh 后会自动下载全量安装包,默认保存在~/.arthas/lib
目录下。修改as.sh脚本可以更改默认保存路径
windows全量安装只需要用浏览器打开下载地址就会提示下载
https://arthas.aliyun.com/download/latest_version?mirror=aliyun
全量安装包的文件夹名称为arthas,目录结构如下
启动
启动之前需要明确要监听哪个java程序,并找到这个java程序的进程id。当前登录用户必须对要监听的进程有可操作权限,否则会报错!
linux下可以使用ps或jps命令可以找到java程序的进程id
ps -ef | grep java jps -mlvV
windows下打开任务管理器可以找到对应程序的进程id
执行命令启动arthas
java -jar arthas-boot.jar
启动后会提示选择监听哪个进程id,输入对应的序号即可。我要监听的进程id是19646,也就是序号2
输入2之后敲回车,启动成功如下图
启动时可以直接指定pid,就不用在启动过程中再输入序号了
java -jar arthas-boot.jar 19646
如果安装时选择了全量安装,那么也可以通过脚本启动,linux系统使用as.sh
脚本,windows系统使用as.bat
脚本
dashbord
dashboard命令可以查看cpu、线程状态,内存信息、GC情况和jdk版本等信息
图中字段解读
ID: Java中的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
NAME: 线程名称
GROUP: 线程组名称
PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
STATE: 线程的状态
CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
TIME: 线程运行总时间,格式为 分:秒
INTERRUPTED: 线程当前的中断状态
DAEMON: 是否是守护线程
监控页面会实时刷新,默认每5000毫秒(5秒)刷新一次。可以通过 - i 参数指定刷新频率,-n
参数指定刷新次数。这个统计会有一定的开销,从截图中也可以看到arthas的cpu占比比较大,所以刷新频率不要太高,建议5秒以上,刷新次数建议10次以内
// 每10秒刷新一次,3次后停止
dashboard -i 10000 -n 3
thread
使用thread命令可以查看线程的状态,查看线程的堆栈
thread命令可以追加参数
thread命令可以追加参数
id:可以查看指定线程id的堆栈信息
-n value:找出最忙的value个线程,并打印堆栈信息
-b:找出当前正在阻塞其他线程的线程
-i value:指定采样cpu占比的时间间隔,默认为100ms。
打印线程id=24的堆栈信息
打印当前最忙的3个线程的堆栈信息
打印正在阻塞其他线程的线程
指定统计线程cpu占比的时间间隔,单位:毫秒。因为统计cpu占比会有一定的资源消耗,所以时间间隔不要太小,建议100毫秒以上
thead -i 200
watch
class-pattern 类名表达式匹配
method-pattern 函数名表达式匹配
express 观察表达式,默认值:{params, target, returnObj}
condition-express 条件表达式
[b] 在函数调用之前观察
[e] 在函数异常之后观察
[s] 在函数返回之后观察
[f] 在函数结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1
trace
参数与watch相似
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 命令执行次数
#cost 方法执行耗时
trace com.zhongjyuan.business.controller.authController sso
tt
方法执行数据的时空隧道
-t 记录每次执行的情况
-n 限制要记录的次数,防止内存溢出
tt -t com.zhongjyuan.business.controller.authController sso
ognl
通过
ognl
直接调用我们的代码
ognl '@com.demo.TestDemo@get()'
如果是调用spring bean中的方法只需要调用获取bean的静态方法即可
ognl '@com.demo.SpringContextHolder@getBean("mobileLoginSuccessHandler").defaultAuthorizationServerTokenServices'
getstatic
通过getstatic命令可以方便的查看类的静态属性
getstatic 类名 属性名
#显示demo.MathGame类中静态属性random
getstatic demo.MathGame random
sm
命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
显示String类加载的方法
sm java.lang.String
显示String中的toString方法详细信息
sm -d java.lang.String toString
monitor
monitor 用来监视一个时间段中指定方法的执行次数,成功次数,失败次数,耗时等这些信息
monitor -c 5
com.zhongjyuan.business.controller.authController sso
jad
使用jad可以对代码进行反编译,反编译过来的代码可能会有语法错误,但不影响阅读。比较贴心的是,反编译过来的代码语法高亮,方便阅读
类名支持全路径,同时也支持模糊匹配,下面两个命令可以达到相同的效果
jad com.zhongjyuan.demo.ThreadBlock
jad *ThreadBlock
也可以对类中的某一个方法进行反编译,只需要在类名后面加上方法名即可
反编译过来的代码默认携带ClassLoader信息,使用 --source-only 选项可以去除ClassLoader信息
jad/mc
使用jad反编译com.zhongjyuan.business.controller.authController输出到/opt/app/modules/authController.java
jad --source-only com.zhongjyuan.business.controller.authController > /opt/app/modules/authController.java
按上面的代码编辑完毕以后,使用mc内存中对新的代码编译
mc /opt/app/modules/authController.java -d /opt/app/modules
使用redefine命令加载新的字节码
redefine /opt/app/modules/authController.class
cls
清空当前屏幕区域
jvm
查看当前 JVM 的信息
sysenv
查看当前JVM的环境属性(System Environment Variables)
查看单个
sysenv JAVA_HOME
quit/exit
使用quit或exit会退出arthas操作界面,但arthas进程还在,这时当你想监听别的java进程时会报错。因为arthas默认使用的是3658端口,所以这时会报错3658端口被别的进程占用。
使用shutdown或stop可以结束arthas,结束后可以监听别的进程id
出现端口被占用报错时,可以再次启动进入刚才监听的进程或者使用telnet进入刚才监听的arthas进程界面,执行shutdown后stop结束监听
telnet localhost 3658
卸载
linu系统中执行如下命令进行卸载
rm -rf ~/.arthas/
rm -rf ~/logs/arthas
window下需要进入C盘当前用户的目录,找到.arthas
目录和logs/arthas
目录进行删除
二、JVM参数使用
JVM介绍
1.JVM区域
JVM区域总体分两类,heap区和非heap区。
heap区又分:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)。
非heap区又分:Code Cache(代码缓存区)、Perm Gen(永久代)、Jvm Stack(java虚拟机栈)、Local Method Statck(本地方法栈)。
2.HotSpot虚拟机GC算法采用分代收集算法:
(1)一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
(2)并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
(3)进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。
分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,则采用复制算法,针对不同情况使用不同算法。
非heap区域中Perm Gen中放着类、方法的定义,jvm Stack区域放着方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式
JVM参数调优
1.默认JVM参数
确保堆内存占用都不超过
80%
,即 heap(堆内存)、ps_eden_space(伊甸园区)、ps_survivor_space(幸存者区)、ps_old_gen(老年代)的使用都不超过80%
2.JVM参数:-Xms3g -Xmx3g -Xss1024k -XX:NewRatio=1 -XX:MetaspaceSize=128m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
3.JVM 参数解析
(1)堆大小设置
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
典型设置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m 设置JVM最大可用内存为3550M。
-Xms3550m 设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g 设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m 设置持久代大小为16m。
-XX:MaxTenuringThreshold=0 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
(2)回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。
- 1) 吞吐量优先的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC 配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100-XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
(3)响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC 设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection 打开对年老代的压缩。可能会影响性能,但是可以消除碎片
常见配置汇总
堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
调优总结
年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统GC信息
- 花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
- 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
- -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
- -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩