Java Agent 调试,agent debug

一、简介

Java agent 是在另外一个 Java 应用(“目标”应用)启动之前要执行的 Java 程序,这样 agent 就有机会修改目标应用或者应用所运行的环境。修改环境的时候做到对于项目中的代码没有入侵性,不需要修改老项目代码即可实现想要的能力,比如常见的skywoking,就是通过这样的方式实现的。比如这篇文章 通过修改字节码实现Java Agent 通过使用 Byte Buddy,便捷地创建 Java Agent 。还有一些功能,比如热更新、arthas 替换class字节码等等。

问题

  • Java agent的实现原理是什么?
  • Java agent 如何调试呢?习惯了现在的直接代码调试?对于agent 有点慌。

二、原理

Java agent 主要是通过Instrumentation实现的。

Instrumentation

简介

java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
    在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。
    “java.lang.instrument”包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入,整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI),而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。当我们谈Debug时,我们在谈什么(Debug实现原理) 这个让我想到了这篇文章,其实感觉和debug的道理一样的,debug的时候可以修改参数,或者数据的信息。

官方文档

This class provides services needed to instrument Java programming language code. Instrumentation is the addition of byte-codes to methods for the purpose of gathering data to be utilized by tools. Since the changes are purely additive, these tools do not modify application state or behavior. Examples of such benign tools include monitoring agents, profilers, coverage analyzers, and event loggers.(这个类提供了测试 Java 编程语言代码所需的服务。仪器是将字节代码添加到方法中,目的是收集工具使用的数据。由于这些更改纯粹是附加的,因此这些工具不会修改应用程序状态或行为。此类良性工具的示例包括监控代理、分析器、覆盖分析器和事件记录器。) 
有两种方法可以获取仪器接口的实例:

  • When a JVM is launched in a way that indicates an agent class. In that case an Instrumentation instance is passed to the premain method of the agent class.(当 JVM 以指示代理类的方式启动时。在这种情况下,检测实例被传递给代理类的 premain 方法。)
  • When a JVM provides a mechanism to start agents sometime after the JVM is launched. In that case an Instrumentation instance is passed to the agentmain method of the agent code.(当 JVM 在 JVM 启动后的某个时候提供启动代理的机制时,检测实例被传递给代理类的 agentmain 方法)

Java Instrumentation-csdn  https://blog.csdn.net/DorMOUSENone/article/details/81781131

三、Java agent debug 实践

Agent 包结构

agent 打包的信息

META-INF.MANIFEST.MF,需要保护一些信息,就是入口类需要传递一个Instrumentation变量给你,JVM 启动的入口类的信息 Premain-Class: com.wangji92.agent.JvmAgentAop,这样标识这个是一个agent。

Manifest-Version: 1.0
Implementation-Title: jvm-agent-aop
Premain-Class: com.wangji92.agent.JvmAgentAop
Implementation-Version: 1.0-SNAPSHOT
Built-By: wangji
Agent-Class: com.wangji92.agent.JvmAgentAop
Can-Redefine-Classes: true
Specification-Title: jvm-agent-aop
Can-Retransform-Classes: true
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_181
Specification-Version: 1.0-SNAPSHOT

agent 入口类小例子

package com.wangji92.agent;import java.lang.instrument.Instrumentation;/*** @author 汪小哥* @date 31-01-2020*/
public class BeforeJvmJavaAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("this is an agent.");System.out.println("this is an agent 和jvm 一起启动");System.out.println("args:" + agentArgs + "\n");Class[] allLoadedClasses = inst.getAllLoadedClasses();for (Class loadedClass : allLoadedClasses) {System.out.println("load class:" + loadedClass.getCanonicalName());if (loadedClass.getClassLoader() != null) {System.out.println("  classloader"+loadedClass.getClassLoader().toString() + "\n");}else{System.out.println("\n");}}}
}

maven 配置,参考arthas

 <build><finalName>simple-before-jvm-agent</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding><showDeprecation>true</showDeprecation></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><executions><execution><goals><goal>single</goal></goals><phase>package</phase><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifestEntries><Premain-Class>com.wangji92.agent.BeforeJvmJavaAgent</Premain-Class><Agent-Class>com.wangji92.agent.BeforeJvmJavaAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes><Specification-Title>${project.name}</Specification-Title><Specification-Version>${project.version}</Specification-Version><Implementation-Title>${project.name}</Implementation-Title><Implementation-Version>${project.version}</Implementation-Version></manifestEntries></archive></configuration></execution></executions></plugin></plugins></build>

Java agent 加载的两种方式

根据Java agent的挂载方式有两种,一种是直接在启动的时候挂载,一种是启动完成之后进行挂载(arthas 就是通过这种方式实现的)。这里实践的时候采用第一种。

JVM 一起启动

直接在启动参数中增加即可

# 通过java 可以查看到帮助文档
-javaagent:<jarpath>[=<选项>]加载 Java 编程语言代理, 请参阅 java.lang.instrument

比如这个

  • simple-before-jvm-agent-jar-with-dependencies.jar  agent 包
  • target-java.jar 目标spring boot 程序
#!/bin/sh -x
dir=$(cd `dirname $0`;pwd)
echo $dir
mvn clean package && \
java -javaagent:$dir/simple-before-jvm-agent/target/simple-before-jvm-agent-jar-with-dependencies.jar=text-args \
-jar $dir/target-java/target/target-java.jar

JVM 启动之后 挂载(参考arthas)

将arthas-agent attach 到目标java进程。
主要代码:com.taobao.arthas.core.Arthas#attachAgent,直到这里,原始的arthas-core的进程处理已经结束了,同事触发了arthas-agent.jar 作为目标java类的代码类触发了arthas-agent Agent-Class(具体可以参考maven xml 配置) agentmain 方法

## 1、attach 到目标进程
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());## 2、在jvm启动后天就agent,第一个是agent的jar位置,第二个传递的参数
## 了解更多可以参考 java.lang.instrument.Instrumentation
virtualMachine.loadAgent(arthasAgentPath,configure.getArthasCore() + ";" + configure.toString());

理解debug

当我们谈Debug时,我们在谈什么(Debug实现原理) 这篇文章被我引用了很多次,其实debug就是JPDA之上进行处理,Java agent 也是通过 Instrumentation 依赖JVMTI 实现对于JVM中的字节码修改,获取JVM 虚拟机的加载的字节码的信息,两者之间太像了,当我们想讨论如何debug java agnet的代码的时候,是否想过agent 和目标的代码有什么异同?对对,都是在同一个JVM中的。无论是我们使用远程debug 还是IDEA上的debug其实实质上都是建立在JPDA的基础上,JPDA(Java Platform Debugger Architecture)是Java平台调试体系结构的缩写。由3个规范组成,分别是JVMTI(JVM Tool Interface),JDWP(Java Debug Wire Protocol),JDI(Java Debug Interface) 。因此都是在一个JVM 里面的,只要目标class 启动的时候开启了debug,那么agent 也可以被debug的。可能在直接使用IDEA debug的时候有一些限制,比如必须在同级的module下面才能debug,那么远程debug就是天然的可以啦!

实战Agent debug(AOP 统计耗时)

agent 入口类

package com.wangji92.agent;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;import java.lang.instrument.Instrumentation;import static net.bytebuddy.matcher.ElementMatchers.*;/*** https://segmentfault.com/a/1190000007253689* https://www.infoq.cn/article/Easily-Create-Java-Agents-with-ByteBuddy/* https://blog.csdn.net/undergrowth/article/details/86493336* https://www.jianshu.com/p/7a5a2c78dab4* https://blog.csdn.net/DorMOUSENone/article/details/81781131* jvmAop 处理** @author 汪小哥* @date 01-02-2020*/
public class JvmAgentAop {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("this is an agent.");System.out.println("this is an agent 和jvm 一起启动");System.out.println("args:" + agentArgs + "\n");new AgentBuilder.Default().type(nameStartsWith("com.wangji92.target").and(not(isInterface())).and(not(isStatic()))).transform(new AgentBuilder.Transformer() {@Overridepublic DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {return builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(CostTimeInterceptor.class));}}).installOn(inst);}
}

byte-buddy AOP 处理

byte-buddy 修改字节码可以参考这个 https://blog.csdn.net/undergrowth/article/details/86493336,
这里只是简单的统计耗死问题。

package com.wangji92.agent;import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;import java.lang.reflect.Method;
import java.util.concurrent.Callable;/*** aop 耗时计算** @author 汪小哥* @date 01-02-2020*/
public class CostTimeInterceptor {@RuntimeTypepublic static Object intercept(@Origin Method method,@SuperCall Callable<?> callable) throws Exception {System.out.println("call " + method.getName());long start = System.currentTimeMillis();try {return callable.call();} finally {System.out.println(method + " took " + (System.currentTimeMillis() - start));}}
}

目标类

/*** @author 汪小哥* @date 30-01-2020*/
@RestController
@RequestMapping("/")
public class JavaAgentTargetController {@RequestMapping("/test/{name}")@ResponseBodypublic String getClassName(@PathVariable String name) {String result = this.getClass().getSimpleName().concat(name);try {Thread.sleep(1000);} catch (InterruptedException e) {//}return result;}
}

启动脚本

这里通过脚本启动,并开启远程debug,然后在IDEA 中添加一个remote debug 填写端口5005,随便访问一个目录类,即可实现debug agent的目的

#!/bin/sh -x
dir=$(cd `dirname $0`;pwd)
echo $dir
mvn clean package && \
java -agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n \
-javaagent:$dir/jvm-agent-aop/target/jvm-agent-aop-jar-with-dependencies.jar=text-args \
-jar $dir/target-java/target/target-java.jar# 本文主要是为了测试 agent的debug 功能,从而了解arthas debug
# 实际上,agent的 remote debug 只要agent和target 在同一个jvm 中就会被加载,从而可以debug。## 通过Byte Buddy 实现字节码修改(skyworking 也是这样的) 参考 https://zhuanlan.zhihu.com/p/84514959
## 使用bytebuddy构建agent   https://segmentfault.com/a/1190000007253689
## 通过使用 Byte Buddy,便捷地创建 Java Agent  https://www.infoq.cn/article/Easily-Create-Java-Agents-with-ByteBuddy/
## byte-buddy 1.9.6 简述及原理1  https://blog.csdn.net/undergrowth/article/details/86493336 github 有demo
## java agent: JVM的层面的"AOP"  https://www.jianshu.com/p/7a5a2c78dab4

四、总结

不断的学习、总结,才能理解更加的深刻。
代码地址:https://github.com/WangJi92/java-agent

更多汪小哥

Java Agent 调试,Java agent debug相关推荐

  1. Jenkins 2.16.3默认没有Launch agent via Java Web Start,如何配置使用

    问题:Jenkins 2.16.3默认没有Launch agent via Java Web Start,如下图所示,而这种启动方式在Windows上是最方便的. 如何设置才能让出来呢? 打开&quo ...

  2. java动态修改class_Java Agent入门学习之动态修改代码

    前言 最近用了一下午总算把Java agent给跑通了,本篇文章记录一下具体的操作步骤,以免遗忘.下面话不多说,来一起看看详细的介绍: 通过java agent可以动态修改代码(替换.修改类的定义), ...

  3. idea 调试java技巧_Intellij IDEA Debug 调试技巧

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 F9:恢复程序 Alt+F10:显示执行断点 F8: 跳到下一步 F7:进入到代码 Alt+shift+F7:强制进入代码 ...

  4. Java断点调试 idea断点调试 debug教程

    IDEA断点调试 Java程序断点调试 文章目录 IDEA断点调试 Java程序断点调试 前言 一.什么情况下需要debug 二.使用idea如何进行debug 三.假设案例分别进行debug教程 1 ...

  5. Java 动态调试技术原理及实践

    调试是发现和减少计算机程序或电子仪器设备中程序错误的一个过程.最常用的断点调试技术会在断点位置停顿,导致应用停止响应.本文将介绍一种Java动态调试技术,希望能对大家有帮助.同时也欢迎读者朋友们一起交 ...

  6. Java 平台调试架构JPDA

    转载自  Java-JPDA 概述 JPDA:Java 平台调试架构(Java Platform Debugger Architecture) 它是 Java 虚拟机为调试和监控虚拟机专门提供的一套接 ...

  7. 【java】Java 动态调试技术原理及实践

    1.概述 转载:Java 动态调试技术原理及实践 一.动态调试要解决的问题 断点调试是我们最常使用的调试手段,它可以获取到方法执行过程中的变量信息,并可以观察到方法的执行路径.但断点调试会在断点位置停 ...

  8. myeclipse调试java

    利用junit调试java代码 先打断点,右键debug as junit,打开debug视图 点击F6,执行过变量所在语句,把鼠标移到变量上就可以看到变量的值 转载于:https://blog.51 ...

  9. Eclipse调试Java的10个技巧【转】

    clipse调试Java的10个技巧 先提三点 不要使用System.out.println作为调试工具 启用所有组件的详细的日志记录级别 使用一个日志分析器来阅读日志 1.条件断点 想象一下我们平时 ...

最新文章

  1. 如何才能做好绩效管理?
  2. Ubuntu 调试的时候,不能查看变量值
  3. python 事件通知模式_请问在 Python 的事件系统中,如何可以通过事件通知立刻终结一个正在运行的子线程?...
  4. fileZilla连接oracle服务器,传DMP文件
  5. Spring-JDK Timer 以及在Spring(4.0以下)中使用JDK Timer
  6. .net post提交后接收返回数据_Ajax提交表单的方式
  7. 2018-2019-2 网络对抗技术 20165301 Exp2 后门原理与实践
  8. SAP 采购流程和销售流程
  9. 用java绘制一个光照球体_HTML5 Canvas一个基本光线行进的球体光照效果
  10. HBuilde H5开发,关于JSON的Storage存储
  11. Linux下编译安装redis,详细教程
  12. AppCompat是什么?
  13. 统计分析干货 | 秩和检验及其两两比较的思路与解析
  14. 普元eos运行环境下的逻辑流及页面流反编译工具
  15. DIY 1U硬件防火墙实录
  16. java rotateright_Java Tetris旋转
  17. 二月,劝程序员不要跳槽!
  18. 每日算法 - 列出24点游戏的所有解法
  19. 《别看了,你学不会的》——Redis原理与实战(一)
  20. ValueError: List argument ‘indices‘ to ‘Sparse Concat‘ Op with length 0 shorter than minimum length2

热门文章

  1. java图形界面设计——求三角形的面积
  2. oracle认证考时间,什么时候是参加Oracle认证考试时间
  3. qdialog 返回值_Qt对话框QDialog
  4. 解除游戏多开限制,关闭互斥体句柄
  5. 嫌学校App太“烂”,极客父母做了开源版本,却遭官方报警
  6. python库函数之scipy.signal——滤波器设计
  7. 微信头像可以加挂件了,快试试
  8. nginx proxy之buffering和cache
  9. VUE mixins使用
  10. python照片墙地图_利用python生成照片墙的示例代码