3 类加载器

您所在的位置:网站首页 jar指定内存 3 类加载器

3 类加载器

2023-03-13 11:01| 来源: 网络整理| 查看: 265

启动时和运行时加载Instrument Agent过程1 Pre-Main使用2 attach方式2 热部署能力3 方法使用时长监控

https://blog.csdn.net/it_zhonghua/article/details/109393260

javaagent是一种能够在不影响正常编译的情况下,修改字节码。java作为一种强类型的语言,不通过编译就不能能够进行jar包的生成。而有了javaagent技术,就可以在字节码这个层面对类和方法进行修改。同时,也可以把javaagent理解成一种代码注入的方式。但是这种注入比起spring的aop更加的优美

通常会用它来做一下Java服务的监控,或者替换其他JVM上的程序,还可以实现虚拟机上的AOP功能

作用:

可以在加载java文件之前做拦截把字节码做修改可以在运行期将已经加载的类的字节码做变更,但是这种情况下会有很多的限制,后面会详细说还有其他的一些小众的功能获取所有已经被加载过的类获取所有已经被初始化过了的类(执行过了clinit方法,是上面的一个子集)获取某个对象的大小将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载将某个jar加入到classpath里供AppClassload去加载设置某些native方法的前缀,主要在查找native方法的时候做规则匹配

主流应用:

1 APM 产品: pinpoint、skywalking、newrelic、听云的 APM 产品等都基于 Instrumentation 实现2 热部署工具:Intellij idea 的 HotSwap、Jrebel 等3 Java 诊断工具:Arthas、Btrace 等

1 JDK 从 JDK5 版本开始引入了java.lang.instrument 包。它可以通过 addTransformer 方法设置一个 ClassFileTransformer,可以在这个 ClassFileTransformer 实现类的转换。2 JDK 1.5 支持静态 Instrumentation,基本的思路是在 JVM 启动的时候添加一个代理(javaagent),每个代理是一个 jar 包,其 MANIFEST.MF 文件里指定了代理类,这个代理类包含一个 premain 方法。JVM 在类加载时候会先执行代理类的 premain 方法,再执行 Java 程序本身的 main 方法,这就是 premain 名字的来源。在 premain 方法中可以对加载前的 class 文件进行修改。这种机制可以认为是虚拟机级别的 AOP,无需对原有应用做任何修改,就可以实现类的动态修改和增强 3 从 JDK 1.6 开始支持更加强大的动态 Instrument,在JVM 启动后通过 Attach API 远程加载

最常用的方法就是addTransformer(ClassFileTransformer transformer)了,这个方法可以在类加载时做拦截,对输入的类的字节码进行修改,其参数是一个ClassFileTransformer接口

addTransformer方法配置之后,后续的类加载都会被Transformer拦截。———————————-已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复

public interface Instrumentation { //增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。 void addTransformer(ClassFileTransformer transformer, boolean canRetransform); //1 在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义, //2 如果在类加载之后,需要使用 retransformClasses 方法重新定义。 //addTransformer方法配置之后,后续的类加载都会被Transformer拦截。 //3 对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。 //类加载的字节码被修改后,除非再次被retransform,否则不会恢复。 void addTransformer(ClassFileTransformer transformer); //删除一个类转换器 boolean removeTransformer(ClassFileTransformer transformer); //是否允许对class retransform boolean isRetransformClassesSupported(); //在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。 void retransformClasses(Class... classes) throws UnmodifiableClassException; //是否允许对class重新定义 boolean isRedefineClassesSupported(); //1 此方法用于替换类的定义,而不引用现有的类文件字节,就像从源代码重新编译以进行修复和继续调试时所做的那样。 //2 在要转换现有类文件字节的地方(例如在字节码插装中),应该使用retransformClasses。 //3 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名 void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException; //获取已经被JVM加载的class,有className可能重复(可能存在多个classloader) @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses();}

Instrument的底层实现依赖于JVMTI(JVM Tool Interface),它是JVM暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果存在),这些接口可以供开发者去扩展自己的逻辑。JVMTIAgent是一个利用JVMTI暴露出来的接口提供了代理启动时加载(Agent On Load)、代理通过Attach形式加载(Agent On Attach)和代理卸载(Agent On Unload)功能的动态库。而Instrument Agent可以理解为一类JVMTIAgent动态库,别名是JPLISAgent(Java Programming Language Instrumentation Services Agent),也就是专门为Java语言编写的插桩服务提供支持的代理

启动时和运行时加载Instrument Agent过程

在启动和运行期都可以加载agent代理,在启动的时候可通过-javaagent参数来执行agent代理,而在运行期就是通过attach这种机制动态load了。

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

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

Java agent以jar包的形式部署在JVM中,jar文件的manifest需要指定agent的类名。根据不同的启动时机,agent类需要实现不同的方法(二选一)

/** * 以vm参数的形式载入,在程序main方法执行之前执行 * 其jar包的manifest需要配置属性Premain-Class */public static void premain(String agentArgs, Instrumentation inst);/** * 以Attach的方式载入,在Java程序启动后执行 * 其jar包的manifest需要配置属性Agent-Class */public static void agentmain(String agentArgs, Instrumentation inst);

一个Java agent既可以在VM启动时加载,也可以在VM启动后加载:

启动时加载:通过vm的启动参数-javaagent:**.jar来启动启动后加载:启动时加载是有一定的缺点的,因为项目在一开始运行的时候不知道到底要不要使用agent,所以jdk1.6之后可以在vm启动后的任何时间点,通过attach api,动态地启动agentagent加载时,Java agent的jar包先会被加入到system class path中,然后agent的类会被system class loader加载。没错,这个system class loader就是所在的Java程序的class loader,这样agent就可以很容易的获取到想要的class。

对于VM启动时加载的Java agent,其premain方法会在程序main方法执行之前被调用,此时大部分Java类都没有被加载(“大部分”是因为,agent类本身和它依赖的类还是无法避免的会先加载的),是一个对类加载埋点做手脚(addTransformer)的好机会。如果此时premain方法执行失败或抛出异常,那么JVM的启动会被终止。

对于VM启动后加载的Java agent,其agentmain方法会在加载之时立即执行。如果agentmain执行失败或抛出异常,JVM会忽略掉错误,不会影响到正在running的Java程序。

1 Pre-Main使用

在 JDK 1.5 中,Java 引入了 java.lang.Instrument 包,该包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型。其中,使用该软件包的一个关键组件就是 Java agent。从名字上看,似乎是个 Java 代理之类的,而实际上,他的功能更像是一个Class 类型的转换器,他可以在运行时接受重新外部请求,对Class 类型进行修改。

参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:

这个 jar 包的MANIFEST.MF 文件必须指定 Premain-Class 项。Premain-Class 指定的那个类必须实现 premain()方法。

重点就在 premain 方法,也就是我们今天的标题。从字面上理解,就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行 -javaagent 所指定 jar 包内 Premain-Class 这个类的 premain 方法,其中,该方法可以签名如下:1.public static void premain(String agentArgs, Instrumentation inst)2.public static void premain(String agentArgs)JVM 会优先加载 1 签名的方法,加载成功忽略 2,如果1 没有,加载 2 方法。这个逻辑在sun.instrument.InstrumentationImpl 类中:

inst 是 Java Class 字节码转换的工具,Instrumentation 常用方法如下:

void addTransformer(ClassFileTransformer transformer, boolean canRetransform);增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。void redefineClasses(ClassDefinition… definitions) hrows ClassNotFoundException, UnmodifiableClassException;在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。boolean removeTransformer(ClassFileTransformer transformer);删除一个类转换器void retransformClasses(Class… classes) throws UnmodifiableClassException在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。

2 attach方式

上面提到,Java agent可以在JVM启动后再加载,就是通过Attach API实现的。当然,Attach API可不仅仅是为了实现动态加载agent,Attach API其实是跨JVM进程通讯的工具,能够将某种指令从一个JVM进程发送给另一个JVM进程。

加载agent只是Attach API发送的各种指令中的一种, 诸如jstack打印线程栈、jps列出Java进程、jmap做内存dump等功能,都属于Attach API可以发送的指令。

使用attach的方式,不需要实现premain函数,需要attachmain。

2 热部署能力

3 方法使用时长监控

继承ClassFileTransformer

参考:

https://www.cnblogs.com/hellotech/p/3952882.html



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3