热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVM源码分析篇|javaagent原理深度解读

本系列文章都是基于HotspotJDK源码,从源码角度来分析我们常见的JVM参数,Java概念以及对应的实现原理及玩法等,希望从根本上来理清Java知识点,我们会不定期地分享这个系

本系列文章都是基于Hotspot/JDK源码,从源码角度来分析我们常见的JVM参数,Java概念以及对应的实现原理及玩法等,希望从根本上来理清Java知识点,我们会不定期地分享这个系列的文章,这些文章可能源于最近碰到的问题,也可能是同学们的提问,甚至有可能是我们突然想到的话题等,有些东西我们现在可能也不一定清楚,但是我们非常愿意花时间去了解清楚并分享给大家。

本文重点讲述javaagent的具体实现,因为它面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础,不过这篇文章里也会讲到JVMTIAgent(c实现的),因为javaagent的运行还是依赖于一个特殊的JVMTIAgent。

对于javaagent或许大家都听过,甚至使用过,常见的用法大致如下:

我们通过-javaagent来指定我们编写的agent的jar路径(./myagent.jar)及要传给agent的参数(mode=test),这样在启动的时候这个agent就可以做一些我们想要它做的事了。

JVM源码分析篇 | javaagent原理深度解读

JVM源码分析篇 | javaagent原理深度解读

javaagent的主要的功能如下:

想象一下可以让程序按照我们预期的逻辑去执行,听起来是不是挺酷的。

JVMTI全称JVM Tool Interface,是jvm暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。

比如说我们最常见的想在某个类的字节码文件读取之后类定义之前能修改相关的字节码,从而使创建的class对象是我们修改之后的字节码内容,那我们就可以实现一个回调函数赋给JvmtiEnv(JVMTI的运行时,通常一个JVMTIAgent对应一个jvmtiEnv,但是也可以对应多个)的回调方法集合里的ClassFileLoadHook,这样在接下来的类文件加载过程中都会调用到这个函数里来了,大致实现如下:

JVMTIAgent其实就是一个动态库,利用JVMTI暴露出来的一些接口来干一些我们想做但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数:

其实我们每天都在和JVMTIAgent打交道,只是你可能没有意识到而已,比如我们经常使用eclipse等工具对java代码做调试,其实就利用了jre自带的jdwp agent来实现的,只是由于eclipse等工具在没让你察觉的情况下将相关参数(类似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)给自动加到程序启动参数列表里了,其中agentlib参数就是用来跟要加载的agent的名字,比如这里的jdwp(不过这不是动态库的名字,而JVM是会做一些名称上的扩展,比如在linux下会去找libjdwp.so的动态库进行加载,也就是在名字的基础上加前缀lib,再加后缀.so),接下来会跟一堆相关的参数,会将这些参数传给Agent_OnLoad或者Agent_OnAttach函数里对应的options参数。

说到javaagent必须要讲的是一个叫做instrument的JVMTIAgent(linux下对应的动态库是libinstrument.so),因为就是它来实现javaagent的功能的,另外instrument agent还有个别名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),从这名字里也完全体现了其最本质的功能:就是专门为java语言编写的插桩服务提供支持的。

instrument agent实现了Agent_OnLoad和Agent_OnAttach两方法,也就是说我们在用它的时候既支持启动的时候来加载agent,也支持在运行期来动态来加载这个agent,其中启动时加载agent还可以通过类似-javaagent:myagent.jar的方式来间接加载instrument agent,运行期动态加载agent依赖的是jvm的attach机制JVM Attach机制实现,通过发送load命令来加载agent。

instrument agent的核心数据结构如下:

这里解释下几个重要项:

正如『概述』里提到的方式,就是启动的时候加载instrument agent,具体过程都在InvocationAdapter.c的Agent_OnLoad方法里,简单描述下过程:

运行时加载的方式,大致按照下面的方式来操作:

上面会通过jvm的attach机制来请求目标jvm加载对应的agent,过程大致如下:

不管是启动时还是运行时加载的instrument agent都关注着同一个jvmti事件---ClassFileLoadHook,这个事件是在读取字节码文件之后回调时用的,这样可以对原来的字节码做修改,那这里面究竟是怎样实现的呢?

先根据jvmtiEnv取得对应的JPLISEnvironment,因为上面我已经说到其实有两个JPLISEnvironment(并且有两个jvmtiEnv),其中一个专门做retransform的,而另外一个用来做其他的事情,根据不同的用途我们在注册具体的ClassFileTransformer的时候也是分开的,对于作为retransform用的ClassFileTransformer我们会注册到一个单独的TransformerManager里。

接着调用transformClassFile方法,由于函数实现比较长,我这里就不贴代码了,大致意思就是调用InstrumentationImpl对象的transform方法,根据最后那个参数来决定选哪个TransformerManager里的ClassFileTransformer对象们做transform操作。

以上是最终调到的java代码,可以看到已经调用到我们自己编写的javaagent代码里了,我们一般是实现一个ClassFileTransformer类,然后创建一个对象注册了对应的TransformerManager里。

这里说的class transform其实是狭义的,主要是针对第一次类文件加载的时候就要求被transform的场景,在加载类文件的时候发出ClassFileLoad的事件,然后交给instrumenat agent来调用javaagent里注册的ClassFileTransformer实现字节码的修改。

类重新定义,这是Instrumentation提供的基础功能之一,主要用在已经被加载过的类上,想对其进行修改,要做这件事,我们必须要知道两个东西,一个是要修改哪个类,另外一个是那个类你想修改成怎样的结构,有了这两信息之后于是你就可以通过InstrumentationImpl的下面的redefineClasses方法去操作了:

在JVM里对应的实现是创建一个VM_RedefineClasses的VM_Operation,注意执行它的时候会stop the world的:

这个过程我尽量用语言来描述清楚,不详细贴代码了,因为代码量实在有点大:

上面是基本的过程,总的来说就是只更新了类里内容,相当于只更新了指针指向的内容,并没有更新指针,避免了遍历大量已有类对象对它们进行更新带来的开销。

retransform class可以简单理解为回滚操作,具体回滚到哪个版本,这个需要看情况而定,下面不管那种情况都有一个前提,那就是javaagent已经要求要有retransform的能力了:

我们从InstrumentationImpl的retransformClasses方法参数看猜到应该是做回滚操作,因为我们只指定了class

不过retransform的实现其实也是通过redefine的功能来实现,在类加载的时候有比较小的差别,主要体现在究竟会走哪些transform上,如果当前是做retransform的话,那将忽略那些注册到正常的TransformerManager里的ClassFileTransformer,而只会走专门为retransform而准备的TransformerManager的ClassFileTransformer,不然想象一下字节码又被无声无息改成某个中间态了。

javaagent除了做字节码上面的修改之外,其实还有一些小功能,有时候还是挺有用的

最近整理了java架构文档和学习笔记文件以及架构视频资料和高清架构进阶学习导图免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。


推荐阅读
author-avatar
止在雲端_495
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有