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

目录
相关文章
|
11天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
27 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
21天前
|
Arthas 监控 Java
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
本文介绍了阿里云 Java Agent 4.x 版本在基于 OTel Java Agent 二次开发过程中的实践与思考,并重点从功能、性能、稳定性、兼容性四个方面介绍了所做的工作。同时也介绍了阿里云可观测团队积极参与开源建设取得的丰厚成果。
153 5
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
|
7天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
9天前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
14 1
|
20天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
42 6
|
19天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
25天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
1月前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
72 5
|
1月前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
34 1