Java Agent入门实战(二)-Instrumentation源码概述

简介: Java Agent入门实战

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。

3e63a4aa4c9ad4c120846d682835c77.png

#[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 的
  • 可以删除修改方法

参考

JVM 源码分析之javaagent 原理完全解读

相关文章
|
1天前
|
分布式计算 Java 大数据
实战:基于Java的大数据处理与分析平台
实战:基于Java的大数据处理与分析平台
|
1天前
|
监控 搜索推荐 Java
实战:基于Java的实时数据流处理平台
实战:基于Java的实时数据流处理平台
|
1天前
|
人工智能 自然语言处理 Java
Java中的自然语言处理应用实战
Java中的自然语言处理应用实战
|
1天前
|
缓存 Java 数据库
实战:构建高性能Java Web应用的技术方案
实战:构建高性能Java Web应用的技术方案
|
1天前
|
Java jenkins 持续交付
Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试
【7月更文挑战第1天】Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试。成功后,Jenkins执行部署任务,发布到服务器或云环境。使用Jenkins能提升效率,保证软件质量,加速上线,并需维护其稳定运行。
11 0
|
1天前
|
Java 编译器 API
Java中的注解:原理与实战
Java中的注解:原理与实战
|
1天前
|
Java API
Java中的函数式编程入门
Java中的函数式编程入门
|
1天前
|
监控 Java API
Java Socket编程入门
Java Socket编程入门
|
1天前
|
Java API 网络安全
Java网络编程入门
Java网络编程入门
|
2天前
|
Java API 开发者
Java网络编程基础与Socket通信实战
Java网络编程基础与Socket通信实战