Instrumentation接口设计初衷是为了收集Java程序运行时的数据,用于监控运行程序状态,记录日志,分析代码用的。接下来从源码的流程来介绍一下
接口拥有的方法 Instrumentation addTransformer ( ClassFileTransformer , boolean ): void p a addTransformer ( ClassFileTransformer ): void @ e removeTransformer ( ClassFileTransformer ): boolean e isRetransformClassesSupported0: boolean @ a retransformClasses ( Class <?>..): void e isRedefineClassesSupported0: boolean e redefineClasses ( ClassDefinition ..): void isModifiableClass ( Class <?>): boolean @getAllLoadedClasses0: Class e getlnitiatedClasses ( ClassLoader ): Class e getObjectSize ( Object ): long appendToBootstrapClassLoaderSearch ( JarFile ): void e appendToSystemClassLoaderSearch ( arFile ): void e isNativeMethodPrefixSupported0: boolean ) setNativeMethodPrefix ( ClassFileTransformer , String ): void
* transformer class . *< P > * This method is intended for use in instrumentation , as described folinkplain Instrumedtation class specification ). aparam transformer the transformer to register aparam canRetransform can this transformer ' s transformatio @ throws java . lang . NullPointerException if passed a < code > null </ c othrows java . lang . UnsupportedOperationException if < code > canRetra * is true and the current configuration of the JVM does not allow * retransformation ([ alink # isRetransformClassesSupportedJ is fals * asince 1.6 VOid addTransformer ( ClassFileTransformer transformer , boolean canRetranst / x * Registers the supplied transformer . *< P > * Same as < code > addTransformer ( transformer , false )</ code >. param transformer the transformer to register
实现类InstrumentationImpl的
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
public synchronized void addTransformer(ClassFileTransformer var1, boolean var2) { if(var1 = null){ throw new NullPointerException("null passed as 'transformer'in addTransformer") else { if(var2){ if(!this.isRetransformClassesSupported()) { throw new UnsupportedOperationException("adding retransformable transformers is not supported in this environment") if(this.mRetransfomableTransformerManager = null){ this.mRetransfomableTransformerManager = new TransformerManager( b: true);} Se this.mRetransfomableTransformerManager.addTransformer(var1); if(this.mRetransfomableTransformerManager.getTransformerCount()= 1){ this.setHasRetransformableTransformers(this.mNativeAgent, var3: true); } else { 进入这个方法 this.mTransformerManager.addTransformer(var1);
从这段代码知道,转换器ClassFileTransformer的实现是存储在TransformerManager的TransformerInfo数组中的,数组初始长度为0,每添加一个,数组长度为原来的长度+1,将原数组内容拷贝到新数组中。
public svnchronized void addTransformer(ClassFileTransformer var1){ TransformerManagerTransformerInfoll var2 = this.mTransformerList; old array new array TransformerManager.TransformerInfo[]var3=newTransformerManager.TransformerInfo[var2.length +1]; System.arraycopy(var2,srcPos:0,var3,destPos: o,var2.length);old arrty to new array var3[var2.length]=newTransformerManager.TransformerInfo(var1);存入新增的ClassFileTransforme this.mTransformerList =var3; }
VirtualMachine.attach
public static VirtualMachineattach(String var0)throws AttachNotSupportedException, IOException { if(var0 = null){ 异常判断 throw new NullPointerException("id cannot be null"); } else { List var1 = AttachProvider.providers(); 进入 if(var1.size() = 0){ throw new AttachNotSupportedException("no providers installed"); } else { AttachNotSupportedException var2 = null; Iterator var3 = var1.iterator(); while(var3.hasNext()){ AttachProvider var4 =(AttachProvider)var3.next(); try { return var4.attachVirtualMachine(var0); } catch(AttachNotSupportedException var6){ var2 =var6; } } throw var2; } /
进入AttachProvider.providers()
,这里面会初始化AttachProvider
,并返回一个AttachProvider List
。
@public static List<AttachProvider> providers() { synchronized(lock){ if (providers = null){ providers = new ArrayList(); 初始化AttachProvider ServiceLoadervar1=ServiceLoader.load(AttachProvider.class,AttachProvider.class.getClassLoader()); Iterator var2 = var1.iterator(); while(var2.hasNext()) { try { providers.add(var2.next()); 保存provider进集合 } catch(Throwable var6){ if(var6 instanceof ThreadDeath){ ThreadDeath var4 =(ThreadDeath)var6; throw var4; } System.err.println(var6); } } 返回一个AttachProvider列表 return Collections.unmodifiableList(providers); }
进入
ServiceLoader.load(AttachProvider.class, AttachProvider.class.getClassLoader());
public static ServiceLoader load(Classs service, ClassLoader loader) 继续跟进 return new ServiceLoader◇ (service, loader);
public void reload(){ providers.clear(); 清空providers集合列表 lookupIterator = new LazyIterator(service,loader);实例化一个迭代器 @private ServiceLoader(class<S> svc, ClassLoader cl){ service =0bjects.requireNonNull(svc,message: "Service interface cannot be null"); loader =(cl= null)?ClassLoader.getSystemClassLoader():cl; acc=(System.getSecurityManager()≠ null)? AccessController.getContext() :null; reload(); 这里使用了外部调用处传来的 AttachProvider.class.getClassler
继续跟进方法 new LazyIterator(service, loader)
VirtualMachine.class x AttachProvider.class x ServiceLoaderjava x Iterator<String> pending = null; String nextName = null; @ private LazyIterator(Class<S> service, ClassLoader loader){ this.service = service; this.loader = loader;} private boolean hasNextService() { if (nextName ≠ null) { return true;} if(configs = null){ 加载META-INF/services/目录下的配置文 try { 件 String fullName = PREFIX + service.getName(); if (loader = null) configs =ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); catch(IOException x){ fail(service,msg: "Error locating configuration files", x);}} while ((pending = null) ll !pending.hasNext()){ if(!configs.hasMoreElements()){ return false; pending = parse(service, configs.nextElement()); nextName = pending.next(); return true;
看一下com.sun.tools
jar包下的META-INF/services/
目录,打开
com.sun.tools.attach.spi.AttachProvider,
可以看到有不同平台操作系统的实现,我的是windows,会调用windos的实现
sun.tools.attach.WindowsAttachProvider
。其他的都被注释掉了。代码看到这,就知道ServiceLoader.load方法最终加载的是
sun.tools.attach.WindowsAttachProvider。
#[solaris]sun.tools.attach.SolarisAttachProvider sun.tools.attach.WindowsAttachProvider #[linux]sun.tools.attach.LinuxAttachProvider#[macosx]sun.tools.attach.BsdAttachProvider#[aix]sun.tools.attach.AixAttachProvider
上文hasNextService()
方法下面的nextService()
return true;} private s nextService(){ if(!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { 加载解析类 c=Class.forName(cn,initialize: false, loader); catch(ClassNotFoundException x){ fail(service, msg: "Provider " + cn + " not found"); if(!service.isAssignableFrom(c)){ fail(service, msg: "Provider " + cn + " not a subtype"); try { S p =service.cast(c.newInstance()); providers.put(cn,p);key是类全限定名,value是类的实例 return pi catch (Throwable x){ fail(service, msg:"Provider"+ cn + " could not be instantiated", x); throw new Error(); // This cannot happen }
回到最初的
VirtualMachine.attach(String var0)
方法,进入
return var4.attachVirtualMachine(var0);的attachVirtualMachine()
内
VirtualMachine.class x WindowsAttachProvider.class x AttachProvider.class x ServiceLoaderjava X com.sun.too Decompiled .cass file, bytecode version: 52.0 (Java 8) @ public VirtualMachine attachVirtualMachine(String var1) throws AttachNotSupportedException, IOException { this.checkAttachPermission(); this.testAttachable(var1); 继续跟进 returr newWindowsVirtualMachine( attachProvider: this,var1); }
static native void init(); static native byte[] generateStub(); static native long openProcess(int var0) throws IOException; static native void closeProcess(long var0) throws IOException; static native long createPipe(String var0) throws I0Exception; static native void closePipe(long var0) throws I0Exception; static native void connectpipe(long var0) throws I0Exception; static native int readPipe(long varo, byte[] var2, int var3, int var4)throws
static native void init(); static native byte[] generateStub(); static native long openProcess(int var0) throws IOException; static native void closeProcess(long var0) throws IOException; static native long createPipe(String var0) throws I0Exception; static native void closePipe(long var0) throws I0Exception; static native void connectpipe(long var0) throws I0Exception; static native int readPipe(long varo, byte[] var2, int var3, int var4)throws
通过 ClassLoader 类中的findNative
方法,可以找到JVM源码中的一些native方法调用名,这样可以关联着JVM源码看底层的C++源码到底做了啥
// Invoked in the VM class linking code. static long findnative(ClassLoader loader,String name){loader:Launcher$AppClassLoader@367name:"Java_sun_tools attach_WindowsVirtualMachine_openProcess" Vector<NativeLibrary> libs = libs (slot 2): size = 1 loader≠null?loader.nativeLibraries:svstemNativeLibraries;loader:Launcher$AppClassLoader@367 svstemNativeLibraries: size =2 svnchronized(libs){ int size = libs.size(); size (slot_4): 1 for(int i =0;i<size; i+){ i(slot_5): 0 size (slot_4): 1 NativeLibrary lib =libs.elementAt(i);lib(slot_6):ClassLoader$NativeLibrary@646 libs(slot_2): size =1 i(slot_5): 0 long entry = lib.find(name); lib(slot_6):ClassLoader$NativeLibrary@646name:"Java_sun_tools_attach_WindowsVirtualMachine_openProcess if (entry ≠0) return entry; } return a: ClassLoader >findNative(0 abc Variables +>注 loader = {Launcher$AppClassLoader@367 >name ="Java sun tools attach WindowsVirtualMachine openProcess" > { } libs (slot 2) = {Vector@645} size = 1 >{.} <monitor> (slot 3) = {Vector@645} size = 11 size (slot 4) =1 Di(slot_5)= 0 v{.} lib (slot 6) = {ClassLoader$NativeLibrary@646} handle=1864630272 jniversion = 65537 fromClass={Class@642} "cass sun.tools.attach.WindowsAttachProvider"... Navigate name = "D:Uava\jdk1.8.0_111\jre\bin\attach.dll" isBuiltin = false
发现在attach.dll中找方法名为
Java_sun_tools_attach_WindowsVirtualMachine_openProcessnative method
try { var3 = Integer.parseInt(var2); } catch(NumberFormatException var6) { throw new AttachNotSupportedException("Invalid process identifier"); } this.hProcess = openProcess(var3); try { 本地方法 enqueue(this.hProcess,stub,(String)null,(String)null); } catch(I0Exception var5){ throw new AttachNotSupportedException(var5.getMessage()); } public void detach() throws I0Exception { synchronized(this){
尝试性的在jdk源码里去找这2个方法
Java_sun_tools_attach_WindowsVirtualMachine_openProcess和Java_sun_tools_attach_WindowsVirtualMachine_enqueue
Java_sun_tools_attach_WindowsVirtualMachine_enqueue Match Case Lldss. SUl LOULS dLLaLH WIIUUW. IILUdiMdLIIe * Method: enqueue *Signature:(JZLiava/lang/String;[Liava/lang/0bject;)V JNIEXPORT void JNICALL Java_sun_tools_attach_WindowsVirtualMachine_enqueue (JNIEnv *env,jclass cls, jlong handle, jbyteArray stub, istring cmd, jstring pipename, jobjectArray args) DataBlock data;
Java_sun_tools_attach_WindowsVirtualMachine_enqueue Match Case n Lldss. SUl LOULS dLLaLH WIIUUW. IILUdiMdLIIe * Method: enqueue *Signature:(JZLiava/lang/String;[Liava/lang/0bject;)V JNIEXPORT void JNICALL Java_sun_tools_attach_WindowsVirtualMachine_enqueue (JNIEnv *env,jclass cls, jlong handle, jbyteArray stub, istring cmd, jstring pipename, jobjectArray args) DataBlock data;
virtualMachine.loadAgent()、 virtualMachine.detach()
源码流程和上述类似,也和平台相关,这里就不在赘述了。
RedefineClasse配置注意事项
可以在运行期对已加载类的字节码做变更,但是这种情况下会有很多的限制 对比新老类,并要求如下:
- 父类是同一个
- 实现的接口数也要相同,并且是相同的接口
- 类访问符必须一致
- 字段数和字段名要一致
- 新增的方法必须是 private static/final 的
- 可以删除修改方法
参考