内容简介:对围绕JVM的各种工具的外挂技术,运用大整理术,大家从茫然,到轻摇纸扇,知道分子。归拢一下,就是C 和 Java两种Agent,SA 和 VirtualMachine 两种 Attach,JMX和PerfData两种Data,两两之间很是混淆,网上好像少了篇简明扼要的科普,所以自己操刀补了一篇,作为上周的直播最近公众号的阅读量很扑,这篇预计更扑,有缘点进来的同学,帮转。
对围绕JVM的各种 工具 的外挂技术,运用大整理术,大家从茫然,到轻摇纸扇,知道分子。
归拢一下,就是C 和 Java 两种Agent,SA 和 VirtualMachine 两种 Attach,JMX和PerfData两种Data,两两之间很是混淆,网上好像少了篇简明扼要的科普,所以自己操刀补了一篇,作为上周的直播 《VJTools如何利用佛性技术完全JVM》 的续集。
最近公众号的阅读量很扑,这篇预计更扑,有缘点进来的同学,帮转。
1. 两种Agent
1.1 Native Agent
以 C/C++代码编写的Agent,用强大的JVMTI(JVM Tool Interface)接口与JVM进行通讯,订阅感兴趣的JVM事件(比如方法出入、线程始末等等),当这些事件发生时,会回调Agent的代码。 JVMTI 同时提供了众多的功能函数,查询和控制 Java 应用的运行状态,包括内存控制和对象获取,线程与锁等等,简直无所不能。
使用者,包括各种Profile工具,如Yourkit,JProfiler,Aysnc-Profiler, 还有动态Reload Class而不重启应用的JRebel。
使用方式 ,可以在启动命令里加入 -agentlib: 或 -agentpath: /path/to/agent.so,也可以用后面讲的VituralMachine.attach()动态加载。
1.2 Java Agent
Java Agent的底层也是JVMTI ,但后门能力就只剩一个AOP 代码植入了:在加载class文件之前做拦截并对字节码做修改。比如AspectJ,单元测试覆盖率的Jacoco,动态重载Class的Spring-Loaded。
典型代码如下:
public class MyAgent { public static void premain(String args, Instrumentation instrumentation){ ClassFileTransformer transformer = new MyClassWeaving(); instrumentation.addTransformer(transformer); } }
另一种逍遥的用法,就是为了随时让 “一段代码” 与 “主应用” 在同一JVM中运行,而不用修改主应用的代码去显式调用。
比如JMX的agent,启动一条TCP侦听线程响应JMX 请求。
比如jolokia的 agent ,启动一条Http侦听线程,响应Restful版的JMX请求。
比如btrace,启动一条TCP线程与btrace client通信,接收client发过来的脚本字节码,进行加载并输出结果。
它有两种启动方式:
一种是在启动时命令行加入 -javaagent:/path/to/agent.jar,根据agent.jar中的MANIFEST.MF文件中的Premain-Class定义,JVM找到相应的MyAgent类,调用其premain函数。
一种是通过后面讲的VM.attach()技术,在任意时刻由外部程序来灵活加载,调用其agentmain函数。
2.两种Attach
两种截然不同方式实现的Attach,本质上都是在跟踪程序与目标JVM之间建立一个沟通的管道,然后在跟踪程序使用特定的API去操作目标JVM。
2.1 Vitural Machine.attach()
跟踪程序通过Unix Domain Socket 与目标JVM的Attach Listener线程进行交互。 Socket 文件为/tmp/.java_pid$PID。
API 接口是com.sun.tools.attach.VirtualMachine 及其子类sun.tools.attach.HotSpotVirtualMachine,在tools.jar中,运行时需要依赖,可以做下面的事情:
● dumpHeap: jmap -dump 效果
● heapHisto: jmap -histo效果
● threadDump: jstack效果
● dataDump: kill-3 效果(jstack + jmap -heap)
● loadAgent: 动态加载C/Java Agent
● agentProperties: 获得已加载Agent的属性
● sytemProperties: 获得System Properties
● setFlag: 动态设置可写的JVM参数(但没几个是可写的)
● printFlag: 打印JVM 参数的值
● jcmd: 执行jcmd命令,具体能干啥见jcmd $PID help
可见, jmap, jmap,jcmd 们默认就是基于这个机制来做事情的。
VJTools的 vjmxcli 的特色之一,如果应用的启动脚本忘了设定启动JMX,可以根据PID attach到应用里,动态的把JMX Agent启动起来,然后通Agent属性获得本地连接地址,嗯, jconsole也是这么干的。
2.2 SA.attach()
著名的SA(Serviceability Agent),用于分析JVM运行时进程的Snapshot数据。Snapshot的意思,就是当SA 开始分析时,整个目标JVM是停顿下来不工作的,让SA可以从容读取进程内存中的数据,直到断开后才会恢复。所以在生产上使用这类工具时,必须先摘除流量。
这个神奇的操作,主要是通过系统调用ptrace实现。ptrace会使内核暂停目标进程并将控制权交给跟踪进程,使跟踪进程得以察看目标进程的内存,详见 ptrace的man ,所以在容器环境下,需要打开ptrace的安全权限。
API的接口,一个是 sun.jvm.hotspot.HotSpotAgent 负责attach, sun.jvm.hotspot.runtime.VM负责操作,在sa-jdi.jar中。
VM类从内存二进制信息中,提取出JVM内部数据结构,包括:
● 内存的getObjectHeap()/getUniverse()
● 处线程的getThreads()
● 永久代内容的getSymbolTable(),getStringTable(), getSystemDictionary()
● 还有很厉害的读内部Native对象值的getTypeDataBase()
jstack,jmap 们默认用前面的VM.attach()模式,与目标JVM的Attach Listener线程通信, 但如果目标JVM已经半死不活,Attach Listener线程无力响应请求时,就可以增加-F 参数,转而使用SA.attach 模式,用ptrace去暴力接管进程, 详细代码见sun.tools.jmap.JMap。看代码你还会发现,因为AttachListener支持的命令有限,所以jmap -heap 打印heap的总结信息时,也是以SA模式进去。
VJTools的 vjmap 是jmap的一个增强,能够独立各个分代中对象的数量大小统计,用以排除新生代对象的干扰,查找内存缓慢泄漏的问题,比如更直接找出Survior区里age>2的对象,里面就使用了VM类的观察者模式回调,和直接内存访问 两种方法来遍历。
SA模式比VM模式做相同事情时要慢一截,非必需时不要用它。还有,如果跟踪程序被kill-9 非正常退出,没有执行中断SA,目标JVM就会一直暂停在那里,Linux下可以执行kill -18 $PID 发送SIGCONT信号重新激活目标进程。
3. 两种Data
3.1 JMX
文章已太多,不再啰嗦。其中vjtools的 vjtop ,如何不停顿JVM的获得线程的CPU、内存信息,获得某条繁忙进程的StackTrace看看它在忙些什么,值得一看。
3.2 PerfData
很多人不知道的一个机制,JVM其实每秒都会将自己的大量统计数据,写入到 /tmp/hsperfdata_$username/$pid 文件中。
用下面指令可以感受下:
jcmd $PID PerfCounter.print
内容包括jvm的基本信息,内存,GC,线程数等等,还有一些JMX中没有暴露的数据,比如包含JVM中所有的停顿的SafePoint信息。
//线程的情况 java.threads.daemon=6 java.threads.live=7 ... // younggen的情况 sun.gc.generation.0.capacity=44695552 sun.gc.generation.0.maxCapacity=715784192 sun.gc.generation.0.minCapacity=44695552 ....
jps,其实就是读取/tmp/hsperfdata_$username/ 目录下所有的文件。
jstat,同样是读取这个神秘的文件。一个很大的好处,就是它只默默读取文件,而不会像JMX那样要与应用程序交互,打扰应用程序的工作。
自己写代码也简单,使用 sun.management.counter.perf.PerfInstrumentation类即可,VJTools里的 vjtop 从JMX,PerfData和/proc/pid 三处地方,综合打印JVM的概况和繁忙线程的情况,里面的PerfData类就是其使用的展示。
PerfData文件是mmap到内存中的,读写都很快,但每次写完还要更新磁盘上的文件元数据比如last modified time,如果遇上磁盘高IO,还是有概率造成JVM被锁定一段时间。所以我们以前通过-XX:+PerfDisableSharedMem禁止了perfdata的写入,不过现在又有点摇摆。
注意,perfdata 和 vm.attach 都需要在/tmp 目录读写文件,如果目标JVM的启动参数重新指定了临时目录,而跟踪程序依然去读取/tmp 目录,也会导致这些机制失效。
4. 小结
VJTools ( https://github.com/vipshop/vjtools ) 的三个工具,是上面的两种Attach, 两种Data的使用样本,大家可以直接阅读源码来加深印象,再顺手点个Star。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。