最近对Java的Profiling和Debugging非常感兴趣,特别是对线上问题的定位方案进行了较深的调研和分析。因为在SOA架构体系中线上问题经常在测试环境不能复现,所以问题的定位具有非常大的挑战。我将业界定位问题的工具和方案都大概的研究了一遍,不论是JProfiler、YourKit、BTrace,还是更底层的Serviceability技术……都广泛用到了
Agent
技术。
在开始之前有必要将HotSpot JVM的Serviceability做个简单的说明。Serviceability可以翻译为服务性,它可以理解为一种能力。这种能力表现在HotSpot虚拟机具备跟踪调试Java进程的能力,可以针对故障分析和隔离。当然作为一名Java程序员,绝大多数时候我们都不需要关注到此层面,但是当我们需要做一个更加深层次的调试工作时我们就绕不开Serviceability,具体的技术有如下几项。
- The Serviceability Agent(SA).Sun私有的一个组件,它可以帮助开发者调试Hotspot。
- jvmstat performance counters.HotSpot通过Sun私有的共享内存机制实现的性能计数器,它是公开给外部给外部进程的。它们也称为perfdata。
- The Java Virtual Machine Tool Interface (JVM TI).它是一套标准的C接口,是 JSR 163 - JavaTM Platform Profiling Architecture 参考实现。JVMTI提供了可用于 debug和profiler的接口;同时,在Java 5/6 中,虚拟机接口也增加了监听(Monitoring),线程分析(Thread analysis)以及覆盖率分析(Coverage Analysis)等功能。JVMTI并不一定在所有的Java虚拟机上都有实现,不同的虚拟机的实现也不尽相同。不过在一些主流的虚拟机中,比如 Sun 和 IBM,以及一些开源的如 Apache Harmony DRLVM 中,都提供了标准 JVMTI 实现。
- The Monitoring and Management interface.Sun的私有实现,它能够实现对HotSpot的监控和管理。
- Dynamic Attach.Sun的私有机制,它允许外部进程在HotSpot中启动一个agent线程,该agent可以将该HotSPot的信息发送给外部进程。
- DTrace.全称Dynamic Tracing,也称为动态跟踪,是由Sun™开发的一个用来在生产和试验性生产系统上找出系统瓶颈的工具,可以对内核(kernel)和用户应用程序(user application)进行动态跟踪并且对系统运行不构成任何危险的技术。在任何情况下它都不是一个调试工具,而是一个实时系统分析寻找出性能及其他问题的工具。DTrace是个特别好的分析工具,带有大量的帮助诊断系统问题的特性。还可以使用预先写好的脚本利用它的功能。用户也可以通过使用 DTrace D语言创建他们自己定制的分析工具,以满足特定的需求。除Solaris系列以外,Dtrace已先后被移植到FreeBSD、NetBSD及Max OS X等操作系统上
- pstack support.pstack是从Solaris发源的实用程序,可以查看core dump文件,调试进程,查看线程运行情况等等。现在,pstack工具也移植到了Linux系统中,比如Red Hat Linux系统、Ubuntu Linux系统等等。HotSpot允许pstack显示Java堆栈帧。
在这一系列的技术中,SA和JVM TI容易产生混淆,SA主要是给JVM开发人员调试虚拟机用的,JVM TI是为Java应用层开发者而设计的,它是标准API,主要用来开发各种调试和分析工具。也就是说SA更底层,如果不触碰JVM虚拟机底层开发,其实绝大多数时候是用不到的。当然也不是说,我们平常完全不会用到SA的功能。比如,jstack -F/m就会使用到SA,我们平常不加-F/-m的时候默认走的是Instrumentation的attach操作。jmap中的部分功能也是SA实现的,具体的没有详细研究。Attach是进程内操作,SA是进程外操作,这便是他们的区别之处。大家可能碰见过这样的例子,就是Java进程hung住了,使用jstack是拿不到threaddump的,这个时候只有加-F才可以拿到,并且拿到的dump数据跟不加-F拿到的数据是有差别的。那是因为attach进JVM之后,要拿到threaddump,需要JVM执行操作才行,但是这个时候JVM已经不能响应任何操作了,所以拿不到。但是进程外就不一样了,它是以一个观察者的方式去查看目标进程,所以能够拿到想要的信息。
虽然Serviceability很强大,但是本篇文章真正想讲的是Instrumentation特性。虽然JDK5中就已经有了premain
,但是真正具有划时代意义的还是JDK6中的agentmain
。因为当不容易重现的问题出现时,你可以不用重启应用,便可以对应用进行问题定位和调试,当然目前只有HotSpot和JRockit虚拟机支持。下面给一个通过环绕织入来实现获取方法执行时间的例子。
1.首先来一个注解,它是用在方法上,表示该方法我需要织入代码来获取执行时间。
1 | package info.yangguo.demo.attch_api.test4; |
2.来一个被织入的测试代码,为了方便测试,classpath动态加载javassist,因为该类库在我后面类的transform中使用到了。
1 | package info.yangguo.demo.attch_api.test4; |
3.当然少不了最重要的Transform,在这里面我使用javassist动态修改了字节码,实现了对目标方法的环绕织入。更多更强大的功能就得各位的具体需求了,比如我们还可以在此处加一个监控的report功能,从而实现实时监控。
1 | package info.yangguo.demo.attch_api.test4; |
4. Agent代码主要是设置transformer
和retransformClasses
。
1 | package info.yangguo.demo.attch_api.test4; |
5.为了方便加了一个自动打包的类,自动生成agent jar,主要是对Transformer、Agent打包,并设置Manifest。这样做的好处是可以通过代码自动化,方便二次开发成内部工具。
1 | package info.yangguo.demo.attch_api.test4; |
6.最后便是Instrumentation的的入口类,将Agent
attach到前面的测试代码中去。代码中我将机器上所有的Java进程给遍历一遍,选择自己感兴趣的进程(TestExample)attach
。
1 | package info.yangguo.demo.attch_api.test4; |