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

目录
相关文章
|
20天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
57 7
|
1月前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
31 4
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
25天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
46 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
12天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
72 13
|
1月前
|
Arthas 监控 Java
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
本文介绍了阿里云 Java Agent 4.x 版本在基于 OTel Java Agent 二次开发过程中的实践与思考,并重点从功能、性能、稳定性、兼容性四个方面介绍了所做的工作。同时也介绍了阿里云可观测团队积极参与开源建设取得的丰厚成果。
206 7
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
|
7天前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
21天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
26天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
53 12
|
20天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。