让咱们继续探秘 Java 热部署。在前文 探秘 Java 热部署二(Java agent premain)中,咱们介绍了 Java agent premain。经过在main方法以前经过相似 AOP 的方式添加 premain 方法,咱们能够在类加载以前作修改字节码的操做,不管是第一次加载,仍是每次新的 ClassLoader 加载,都会通过 ClassFileTransformer 的 transform 方法,也就是说,均可以在这个方法中修改字节码,虽然他的方法名是 premain ,可是咱们确实能够利用这个特性作这个事情。html
在文章的最后,咱们也提到了,虽然相比较在自定义类中修改字节码,premain 没有什么侵入性,对业务透明,可是美中不足的是,他还须要在启动的时候增长参数。java
咱们还提到了,premain 虽然能够热部署,可是还须要从新建立类加载器,虽然,这的确也符合 JVM 关于类的惟一性的定义。可是,有一种状况,若是使用的是系统类加载器,咱们也没法建立新的ClassLoader对象。那么咱们也就没法从新加载类了,怎么办呢?还好 Java 6 为咱们提供了一种方法,也就是今天的主角 agentmain。git
和 premain 师出同门,咱们知道,premain 只能在类加载以前修改字节码,类加载以后无能为力,只能经过从新建立ClassLoader 这种方式从新加载。而 agentmain 就是为了弥补这种缺点而诞生的。简而言之,agentmain 能够在类加载以后再次加载一个类,也就是重定义,你就能够经过在重定义的时候进行修改类了,甚至不须要建立新的类加载器,JVM 已经在内部对类进行了重定义(重定义的过程至关复杂)。github
可是这种方式对类的修改是由限制的,对比原来的老类,由以下要求:jvm
1.父类是同一个;maven
能够看的出来,相比较从新建立类加载器,限制仍是挺多的,最重要的字段是没法修改的。所以,使用的时候要注意。ide
可是,agentmain 还有一个强大的特色,就是目标程序什么都不须要管,就可以被代理。还记得 premain 是如何使用的吗?须要在目标应用启动的时候增长 -javaagent 参数。虽然说没有侵入性,但相比 agentmain 而言,仍是有侵入性的,毕竟 agentmain 什么都不要。目标程序独立运行,什么都不用管。源码分析
那咱们就来试试吧!测试
agentmain 使用步骤以下:google
让咱们按着步骤来一遍吧!
Manifest-Version: 1.0 Can-Redefine-Classes: true Agent-Class: cn.think.in.java.clazz.loader.asm.agent.AgentMainTraceAgent Can-Retransform-Classes: true
public class AgentMainTraceAgent { public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { System.out.println("Agent Main called"); System.out.println("agentArgs : " + agentArgs); inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("agentmain load Class :" + className); return classfileBuffer; } }, true); inst.retransformClasses(Account.class); }
上面的类逻辑很简单,打印 Agent Main called,并打印参数,而后添加一个类转换器,转换器中的逻辑只是打印字符串,为了简单起见,并无修改字节码(各位可自行使用ASM 等类库修改)。最后一点很重要,执行了 inst.retransformClasses(Account.class); 这段代码的意思是,从新转换目标类,也就是 Account 类。也就是说,你须要从新定义哪一个类,须要指定,不然 JVM 不可能知道。还有一个相似的方法 redefineClasses ,注意,这个方法是在类加载前使用的。类加载后须要使用 retransformClasses 方法。
这段代码很重要:
class JVMTIThread { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list) { if (vmd.displayName().endsWith("AccountMain")) { VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); virtualMachine.loadAgent("E:\\self\\demo\\out\\artifacts\\test\\test.jar ", "cxs"); System.out.println("ok"); virtualMachine.detach(); } } } }
注意:写这段代码的时候 IDE 可能提示找不到 jar 包,这时候将 jdk/lib/tools.jar 添加的项目的 classpath 中,具体请自行 google。
该main方法步骤以下:
如何测试呢?
首先看测试类,也就是AccountMain类:
class AccountMain { public static void main(String[] args) throws InterruptedException { for (;;) { new Account().operation(); Thread.sleep(5000); } } }
当咱们一切准备就绪,启动 AccountMain 后,再启动 JVMTIThread 类,结果以下:
能够看到,执行了1遍 operation方法后,咱们启动了 attach jvm,随后,agentmain 方法就被执行了,并打印了咱们咱们传递的字符串参数,同时也进入到了 ClassFileTransformer 方法中,表示从新加载了 Account 类。有点遗憾的是,限于篇幅,咱们没有修改字节码。不过楼主仍是展现一下楼主修改字节码的结果吧,假设楼主想统计 operation 方法的时长,楼主将使用 ASM 增长一段统计时长的代码:
public class TimeStat { static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void start() { threadLocal.set(System.currentTimeMillis()); } public static void end() { long time = System.currentTimeMillis() - threadLocal.get(); System.out.print(Thread.currentThread().getStackTrace()[2] + "方法耗费时间: "); System.out.println(time + "毫秒"); } }
而后修改 agentmain 方法中ClassFileTransformer 的transform 逻辑,也就是在这里修改字节码。
运行结果以下:
能够看到,首先重定义了 Account 类,又主动加载了 TimeStat 类,而后生效,在 operation 字符串后面打印了方法的耗时。
经过对 agentmain 的使用,咱们感觉到了他的强大,在目标程序丝绝不改动的,甚至连启动参数都不加的状况下,能够修改类,而且是运行后修改,并且不从新建立类加载器。其主要得益于 JVM 底层的对类的重定义,关于底层代码解释,Jvm 大神寒泉子有篇文章 JVM源码分析之javaagent原理彻底解读 ,详细分析了 javaagent 的原理。但 agentmain 有一些功能上的限制,好比字段不能修改增减。因此,使用的时候须要权衡,到底使用哪一种方式实现热部署。
说了这么久的热部署,其实就是动态或者说运行时修改类,大的方向说有2种方式:
不管是哪一种,都须要字节码修改的库,好比ASM,javassist ,cglib 等,不少。总之,经过java.lang.instrument 包配合字节码库,能够很方便的动态修改类,或者进行热部署。
good luck!!!!!
参考: JVM源码分析之javaagent原理彻底解读
《实战Java 虚拟机》
javaagent
Instrumentation 新功能