【JVM性能分析】「Arthas技术专题」安装入门及基础指令介绍

简介: 【JVM性能分析】「Arthas技术专题」安装入门及基础指令介绍

JVM性能分析


Arthas特性介绍


  • Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。
  • Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。



Arthas解决范畴


  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从JVM内查找某个类的实例?



Arthas快速开始


使用arthas-boot(推荐)


下载arthas-boot.jar,然后用java -jar的方式启动


下载文件
curl -O https://arthas.aliyun.com/arthas-boot.jar
wget https://alibaba.github.io/arthas/arthas-boot.jar
复制代码


执行命令
java -jar arthas-boot.jar
复制代码



打印帮助信息:


java -jar arthas-boot.jar -h
复制代码


如果下载速度比较慢,可以使用aliyun的镜像:

java -jar arthas-boot.jar --repo-mirror aliyun --use-http
复制代码




使用as.sh


Arthas 支持在 Linux/Unix/Mac 等平台上一键安装:

curl -L https://arthas.aliyun.com/install.sh | sh
复制代码
  • 下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。
  • 直接在shell下面执行./as.sh,就会进入交互界面。
  • 也可以执行./as.sh -h来获取更多参数信息。



Arthas基础指令


dashboard


在Arthas的命令行界面,输入dashboard,会实时展示当前JVM应用服务的多线程状态、JVM各内存区域、GC情况等信息。


image.png

image.png


参数信息

image.png


数据说明


  • ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应。
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程的cpu使用率。
  • 比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则cpu使用率=100/1000=10%
  • DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为秒
  • TIME: 线程运行总CPU时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程



JVM内部线程


  • **Java8之后支持获取JVM内部线程CPU时间,这些线程只有名称和CPU时间,没有ID及状态等信息(显示ID为-1)。 **
  • 内部线程观测到JVM活动,如GC、JIT编译等占用CPU情况,方便了解JVM整体运行状况。
  • 当JVM堆(heap)/元数据(metaspace)空间不足或OOM时,可以看到GC线程的CPU占用率明显高于其他的线程。


当执行trace/watch/tt/redefine等命令后,可以看到JIT线程活动变得更频繁。因为JVM热更新class字节码时清除了此class相关的JIT编译结果,需要重新编译


JVM内部线程包括下面几种:


  • JIT编译线程: 如 C1 CompilerThread0, C2 CompilerThread0
  • GC线程: 如GC Thread0, G1 Young RemSet Sampling
  • 其它内部线程: 如VM Periodic Task Thread, VM Thread, Service Thread



thread


查看当前线程信息,查看线程的堆栈。



参数说明

image.png

cpu使用率是如何统计出来的?


cpu使用率与linux 命令top -H -p <pid> 的线程%CPU类似,一段采样间隔时间内,当前JVM里各个线程的增量cpu时间与采样间隔时间的比例。


实际案例


  • 输入thread会显示所有线程的状态信息
  • 输入thread -n 3会显示当前最忙的3个线程,可以用来排查线程CPU消耗
  • 输入thread -b 会显示当前处于BLOCKED状态的线程,可以排查线程锁的问题


工作原理说明:


  • 首先第一次采样,获取所有线程的CPU时间
java.lang.management.ThreadMXBean#getThreadCpuTime()
sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
复制代码
  • 然后睡眠等待一个间隔时间(默认为200ms,可以通过 -i 指定间隔时间)
  • 再次第二次采样,获取所有线程的CPU时间,对比两次采样数据,计算出每个线程的增量CPU时间


线程CPU使用率 = 线程增量CPU时间 / 采样间隔时间 * 100%

注意: 这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。


最忙的前N个线程并打印堆栈:


image.png


  • 没有线程ID,包含[Internal]表示为JVM内部线程,参考dashboard命令的介绍。
  • cpuUsage为采样间隔时间内线程的CPU使用率,与dashboard命令的数据一致。
  • deltaTime为采样间隔时间内线程的增量CPU时间,小于1ms时被取整显示为0ms。
  • time 线程运行总CPU时间。

输出所有相关的线程的明细:


thread -all



image.png


查看某个线程号对应的堆栈:
thread pid


image.png

查看指定状态的线程

thread –state


image.png


jvm


输入jvm,查看jvm详细的性能数据


Runtime

image.png


Memory/OperationSystem/Thread

image.png


jad


对类进行反编译:

image.png



getstatic


  • 推荐直接使用ognl命令,更加灵活。
  • 通过getstatic命令可以方便的查看类的静态属性。使用方法为getstatic class_name field_name


sc


  • 查看JVM已加载的类信息。
  • “Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d]、[E]、[f] 和 [x:]。



mc


Memory Compiler/内存编译器,编译.java文件生成.class。

mc /tmp/TestSample.java
复制代码



trace


通常说一个接口性能不好,其实就是接口响应时间比较长造成的,具体代码中哪个函数耗时比较长呢?可以使用trace功能来监控一下


image.png


解释:


  • -j 参数可以过滤掉jdk自身的函数

sc


查找JVM中已经加载的类

$ sc -d org.springframework.web.context.support.XmlWebApplicationContext
 class-info        org.springframework.web.context.support.XmlWebApplicationContext
 code-source       /Users/xxx/work/test/WEB-INF/lib/spring-web-3.2.11.RELEASE.jar
 name              org.springframework.web.context.support.XmlWebApplicationContext
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       XmlWebApplicationContext
 modifier          public
 annotation
 interfaces
 super-class       +-org.springframework.web.context.support.AbstractRefreshableWebApplicationContext
                     +-org.springframework.context.support.AbstractRefreshableConfigApplicationContext
                       +-org.springframework.context.support.AbstractRefreshableApplicationContext
                         +-org.springframework.context.support.AbstractApplicationContext
                           +-org.springframework.core.io.DefaultResourceLoader
                             +-java.lang.Object
 class-loader      +-org.apache.catalina.loader.ParallelWebappClassLoader
                     +-java.net.URLClassLoader@6108b2d7
                       +-sun.misc.Launcher$AppClassLoader@18b4aac2
                         +-sun.misc.Launcher$ExtClassLoader@1ddf84b8
 classLoaderHash   25131501
复制代码


sm


查看已加载类的方法信息

  • “Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
  • sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。


image.png


$ sm java.lang.String
java.lang.String-><init>
java.lang.String->equals
java.lang.String->toString
java.lang.String->hashCode
java.lang.String->compareTo
java.lang.String->indexOf
java.lang.String->valueOf
java.lang.String->checkBounds
java.lang.String->length
java.lang.String->isEmpty
java.lang.String->charAt
java.lang.String->codePointAt
java.lang.String->codePointBefore
java.lang.String->codePointCount
java.lang.String->offsetByCodePoints
java.lang.String->getChars
java.lang.String->getBytes
java.lang.String->contentEquals
java.lang.String->nonSyncContentEquals
java.lang.String->equalsIgnoreCase
java.lang.String->compareToIgnoreCase
java.lang.String->regionMatches
java.lang.String->startsWith
java.lang.String->endsWith
java.lang.String->indexOfSupplementary
java.lang.String->lastIndexOf
java.lang.String->lastIndexOfSupplementary
java.lang.String->substring
java.lang.String->subSequence
java.lang.String->concat
java.lang.String->replace
java.lang.String->matches
java.lang.String->contains
java.lang.String->replaceFirst
java.lang.String->replaceAll
java.lang.String->split
java.lang.String->join
java.lang.String->toLowerCase
java.lang.String->toUpperCase
java.lang.String->trim
java.lang.String->toCharArray
java.lang.String->format
java.lang.String->copyValueOf
java.lang.String->intern
复制代码

stack

查看方法 test.arthas.TestSample#execute 的调用堆栈:

$ stack test.arthas.TestSample execute
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 286 ms.
ts=2018-09-18 10:11:45;thread_name=http-bio-8080-exec-10;id=d9;is_daemon=true;priority=5;TCCL=org.apache.catalina.loader.ParallelWebappClassLoader@25131501
    @test.arthas.TestSample.execute()
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
        ...
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:451)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1121)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
复制代码



retransform


加载外部的.class文件,retransform 热更新jvm已加载的类。

retransform /tmp/TestSample.class
retransform -c 327a647b /tmp/TestSample.class /tmp/Test\$Inner.class
复制代码



retransform指定的 .class 文件

$ retransform /tmp/TestSample.class
retransform success, size: 1, classes:
com.TestSample
复制代码



加载指定的 .class 文件,然后解析出class name,再retransform jvm中已加载的对应的类。每加载一个 .class 文件,则会记录一个 retransform entry。

如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry.



查看 retransform entry


$ retransform -l
Id              ClassName       TransformCount  LoaderHash      LoaderClassName
1               com.TestSample   1               null            null
复制代码


TransformCount 统计在 ClassFileTransformer#transform 函数里尝试返回 entry对应的 .class文件的次数,但并不表明transform一定成功。


删除指定 retransform entry
需要指定 id:
retransform -d 1
复制代码



删除所有 retransform entry

retransform --deleteAll 显式触发 retransform

$ retransform --classPattern com.TestSample
retransform success, size: 1, classes:
com.TestSample
复制代码



注意:对于同一个类,当存在多个 retransform entry时,如果显式触发 retransform ,则最后添加的entry生效(id最大的)。



消除 retransform 的影响


如果对某个类执行 retransform 之后,想消除影响,则需要:


删除这个类对应的 retransform entry



重新触发retransform


如果不清除掉所有的 retransform entry,并重新触发 retransform ,则arthas stop时,retransform过的类仍然生效












相关文章
|
4月前
|
算法 Java
太狠了!阿里技术专家撰写的电子版JVM&G1 GC实战,颠覆了传统认知
JVM是Java语言可以跨平台、保持高发展的根本,没有了 JVM, Java语言将失去运行环境。针对 Java 程序的性能优化一定不可能避免针对JVM 的调优,随着 JVM 的不断发展,我们的应对措施也在不断地跟随、变化,内存的使用逐渐变得越来越复杂。所有高级语言都需要垃圾回收机制的保护,所以 GC 就是这么重要。
|
2月前
|
监控 Oracle Java
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
40 0
|
3月前
|
Oracle Java 编译器
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
48 1
|
2月前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
91 0
|
4天前
|
存储 Java 索引
深入浅出JVM(十)之字节码指令(下篇)
深入浅出JVM(十)之字节码指令(下篇)
|
4天前
|
存储 Java 索引
深入浅出JVM(九)之字节码指令(上篇)
深入浅出JVM(九)之字节码指令(上篇)
|
6天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
19 0
|
19天前
|
缓存 监控 Java
Java从入门到精通:3.3.1性能优化与调优——学习Java的性能优化技巧,如JVM调优
Java从入门到精通:3.3.1性能优化与调优——学习Java的性能优化技巧,如JVM调优
|
26天前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【4月更文挑战第17天】本文探讨了JVM性能调优的关键技术,包括内存模型调优(关注堆内存和垃圾回收),选择和优化垃圾收集器,利用JVM诊断工具进行问题定位,以及实战调优案例。强调了开发者应理解JVM原理,善用工具,结合业务场景进行调优,以应对高并发和大数据量的挑战。调优是持续的过程,能提升系统稳定性和效率。
|
1月前
|
算法 安全 前端开发
JVM的基础入门(上)
JVM的基础入门(上)
58 0