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 原理完全解读

相关文章
|
2天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
5天前
|
运维 自然语言处理 供应链
Java云HIS医院管理系统源码 病案管理、医保业务、门诊、住院、电子病历编辑器
通过门诊的申请,或者直接住院登记,通过”护士工作站“分配患者,完成后,进入医生患者列表,医生对应开具”长期医嘱“和”临时医嘱“,并在电子病历中,记录病情。病人出院时,停止长期医嘱,开具出院医嘱。进入出院审核,审核医嘱与住院通过后,病人结清缴费,完成出院。
22 3
|
8天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
37 5
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
10天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
12天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
37 3
|
14天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
13天前
|
移动开发 前端开发 JavaScript
java家政系统成品源码的关键特点和技术应用
家政系统成品源码是已开发完成的家政服务管理软件,支持用户注册、登录、管理个人资料,家政人员信息管理,服务项目分类,订单与预约管理,支付集成,评价与反馈,地图定位等功能。适用于各种规模的家政服务公司,采用uniapp、SpringBoot、MySQL等技术栈,确保高效管理和优质用户体验。
|
14天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
15 1
|
6月前
|
存储 Java
【JAVA学习之路 | 进阶篇】集合框架概述
【JAVA学习之路 | 进阶篇】集合框架概述

热门文章

最新文章