JVM中Instrumentation实现

想必不少人听说过javaagent,但是很少人听说Instrumentation,其实Instrumentation就是javaagent的实现机制,说到Instrumentation,就必须想了解java的attach机制,那就先说下attach的实现。

大家进行jstack的时候,是不是经常看到两个线程Signal DispatcherAttach Listener线程,可能不知道是干嘛的吧,这两个线程是实现attach的关键所在,其中前者是在jvm启动的时候就会创建的后者只有接收过attach请求的时候vm才会创建,顾名思义,Signal Dispatcher是分发信号的Attach Listener 是处理attach请求的,那么两者有什么关系呢,当我们执行attach方法的时候,会向目标vm发出一个SIGQUIT 的信号,目标vm收到这个信号之后就会创建Attach Listener线程了,当然jvm保证了不会多创建。

Attach机制说得简单点就是提供A进程可以连上B进程(当然是java进程),创建socket进行通信,A通过发命令给B,B然后对命令进行截取从自己的vm中获取信息发回给客户端vm,但是并不是随便发指令都会处理的,那么attach Listener接收哪些命令呢,如下所示:

static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties",  get_agent_properties },
  { "datadump",         data_dump },
  { "dumpheap",         dump_heap },
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",       thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { "jcmd",             jcmd },
  { NULL,               NULL }
};

Instrumentation的实现其实主要使用了load这个指令,它用来实现让target vm动态加载agentlib,Instrumentation的实现在一个名为libinstrument.dylib的动态lib库,linux下是libinstrument.so,它是基于JVMTI接口实现的,因此在对其进行load的时候会创建一个agent实例,并往JVMTI环境注册一些回调方法,比如监听类文件加载的事件vm初始化完成事件等,执行Agent_OnAttach,这里会创建一个Instrumentation实例并返回给用户供大家扩展Instrumentation,比如增加一些transform,并会执行Instrumentation实例的 loadClassAndCallAgentmain方法,该方法主要执行agent的MF文件里定义的 Agent-Class类的agentmain方法,当vm初始化完毕之后,会调用loadClassAndCallPremain方法,该方法主要执行agent的MF文件里定义的Agent-Class类的premain方法。在类进行加载的时候会调用Instrumentation的transform方法,可以看看参数里有个byte数组,这个数组其实就是正在加载的class字节码,所以如果要字节码增强在这里就可以入手啦,甚至可以实现偷天换日。

如果在vm启动过程中加载agent,那么会在vm初始化过程中先执行libinstrument.dylib里InvocationAdapter.c的Agent_OnLoad方法,该方法主要:实例化agent,解析agent的MF文件,将相关属性取出来,并注册JVMTI的一些回调函数,在vm初始化完成之后,会通过回调函数去实例化Instrumentation实现对象,设置ClassFileLoadHook函数,并调用Pre-Main指定类的premain方法。

如果在运行期通过attach api来load agent,那么会在收到load指令之后,会调用InvocationAdapter.c的Agent_OnAttach方法,其实现基本和Agent_OnLoad一致,只是会调用Agent-Class的agentmain方法,还有点不同就是对vm init事件没有再关注(都运行期了,关注也没用),而是直接对ClassFileLoad关注,也不会再调用Pre-Main指定的类的premain方法(顾名思义,是在执行main方法之前执行的,所以运行期搞执行Pre-Main的class也不妥)。

时间: 2024-10-14 23:32:48

JVM中Instrumentation实现的相关文章

JVM中的STW和CMS

Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外).Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互:这些现象多半是由于gc引起. GC时的Stop the World(STW)是大家最大的敌人.但可能很多人还不清楚,除了GC,JVM下还会发生停顿现象. JVM里有一条特殊的线程--VM Threads,专门用来执行一些特殊的VM Operation

JVM中class文件探索与解析(一)

一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何加载和运行的. (1)首先,编写简单代码,对其编译生成的class文件进行研究,其java代码如下: 1 public class test { 2 private static int count = 0; 3 public static void recursion(){ 4 count++;

简单谈谈JVM中的GC(中)

书接上文,在了解JVM的分代模型后,接着来简单聊聊JVM中GC算法和不同的GC收集器[求关注] GC回收算法 一个GC回收算法通常会做这么几件事: 1.遍历内存,找到被引用的对象 2.清理掉这些未被标记对象的内存 3.被清理掉的内存放回内存中,供其他地方使用 上文也提及过,目前JVM中的搜索引用对象是用的根搜索方式,再重复引用下: 所有的Java对象构成一颗近似"搜索树"的结构,有一个root根节点,每次从root出发向下搜索,当整个树遍历完成后,那些不在其中的变量则视为"垃

深入Java虚拟机:JVM中的Stack和Heap

转自:http://www.cnblogs.com/laoyangHJ/archive/2011/08/17/gc-Stack.html —————————————————————————————————————————————— 在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认识Stack和Heap,并通过这些原理认清Java中静态方法和静态属性的问题. 一般,JVM的内存分为两部分:Stack和Heap. Stack(栈)是JVM的内

浅析JVM中的GC日志

目录 一.GC日志的格式分析 二.运行时开启GC日志 一.GC日志的格式分析 在讲述GC日志之前,我们先来运行下面这段代码 1 package com.example; 2 3 public class TestMinorGC { 4 private static final int _1MB = 1024*1024; 5 6 public static void testAllocation() { 7 byte[] allocation1, allocation2, allocation3,

jvm中的垃圾回收

一.垃圾回收的概念 Java中的内存回收即Jvm运行时的内存的回收,需要回收的区域有方法区和Java堆.由于程序计数器,Java虚拟机栈和本地方法栈在方法结束或者是线程结束时会自动进行回收所以无须考虑回收.为什么需要垃圾回收呢?因为电脑中的内存的大小是有限而固定的,在运行过程中由于类的加载和创建,内存中已使用的内存会越来越大,导致后来的程序执行时无法进行分配内存进行执行,此时就需要进行垃圾回收,将已经使用的内存区域中没有在使用的数据清除,从而使后来的程序能够正常运行. 二.如何判断一个对象可以进

JVM中的Stack和Heap

Stack: 是内存指令区.Java基本数据类型,Java指令代码,常量都保存在stack中,方法是指令也保存在stack中. 由于stack是内存是顺序分配,而且定长,不存在内存回收问题.存取速度快. Heap: 是内存数据区.管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例.而对象实例在Heap 中分配好以后,需要在Stack中保存一个4字节的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例. 而Heap 则是随机分配内存,不定长度,存在内存分配和回

定位JVM中占CPU较高的堆栈

疑问: 在linux中,使用top命令,发现某java进程占用CPU较高,如何定位到是那个线程,执行哪些代码导致的呢? 第一步:找到占用CPU较高的进程号,使用top命令查看. 从上图可以看到,java进程号为759的进程占用cpu特别高. 第二步:查看当前进程759所有线程占用cpu情况 使用top –Hp pid 来查看,shift + t命令展示或关闭线程情况. 从上图,可以看出760的java线程占用CPU较高. 第三步:查看760线程的堆栈信息. 使用命令 jstack 759 | g

从字节码指令看重写在JVM中的实现

Java是解释执行的,包括动态链接的特性,都给解析或运行期间提供了很多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令如何确定方法调用的版本是本文如下要探讨的主要内容,全文围绕一个多态的简单举例来看在JVM中是如何实现的. 先简单介绍几个概念.对于字节码执行模型及字节码指令集的相关概念可以参考之前的一篇介绍http://blog.csdn.net/lijingyao8206/article/details/46562933. 一.方法调用的