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月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
65 7
|
12天前
|
自然语言处理 Java
Java中的字符集编码入门-增补字符(转载)
本文探讨Java对Unicode的支持及其发展历程。文章详细解析了Unicode字符集的结构,包括基本多语言面(BMP)和增补字符的表示方法,以及UTF-16编码中surrogate pair的使用。同时介绍了代码点和代码单元的概念,并解释了UTF-8的编码规则及其兼容性。
82 60
|
6天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
67 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
30天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
116 13
|
1月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
Gemini 2.0:谷歌推出的原生多模态输入输出 + Agent 为核心的 AI 模型
谷歌最新推出的Gemini 2.0是一款原生多模态输入输出的AI模型,以Agent技术为核心,支持多种数据类型的输入与输出,具备强大的性能和多语言音频输出能力。本文将详细介绍Gemini 2.0的主要功能、技术原理及其在多个领域的应用场景。
160 20
Gemini 2.0:谷歌推出的原生多模态输入输出 + Agent 为核心的 AI 模型
|
1月前
|
人工智能 API 语音技术
TEN Agent:开源的实时多模态 AI 代理框架,支持语音、文本和图像的实时通信交互
TEN Agent 是一个开源的实时多模态 AI 代理框架,集成了 OpenAI Realtime API 和 RTC 技术,支持语音、文本和图像的多模态交互,具备实时通信、模块化设计和多语言支持等功能,适用于智能客服、实时语音助手等多种场景。
154 15
TEN Agent:开源的实时多模态 AI 代理框架,支持语音、文本和图像的实时通信交互