在定位公司问题的时候,需要了解一下skywalking的相关知识,而agent就提上了日程。

官网文档

Agent技术是Jdk在1.5版本之后,所提供的一个在jvm启动前后对部分java类代理加强的机制。由于是直接修改字节码,并不会对业务代码有注入,所以可以很好的应用于监控或者热部署等场景。

正常所提到的Agent一般都是部署成jar包的样子,比如agent-1.0-SNAPSHOT.jar。

在这个jar包中,要添加一个MANIFEST.MF文件,在文件中指定jar包的代理类,比如下面代码中的Premain-Class。

在对应的代理类,要实现一个permain方法或者agentmain方法,这样jvm可以通过MANIFEST找到类,通过类再找到对应的方法,从而进行加强,所以加强逻辑是在permain方法或者agentmain方法内部实现的。

Manifest-Version: 1.0
Built-By: qisi
Premain-Class: com.qisi.agent.InterviewAgent
Agent-Class: com.qisi.agent.InterviewAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: byte-buddy-1.10.22.jar
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_332
public class InterviewAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
}
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
}
}

而如果在permain或者agentmain方法打上debug可以发现,执行时是通过sun.instrument.InstrumentationImpl#loadClassAndCallPremain和sun.instrument.InstrumentationImpl#loadClassAndCallAgentmain两个方法通过反射来执行到我们指定的类的。

Agent技术有两种场景,一种是在jvm启动之前,通过-javaagent:path来指定jar包,像是skywalking就是采用的这种方式;另一种则是在jvm启动之后,通过attach指定的进程,对jvm中的类进行加强,arthas就是采用的这种方式。

在具体介绍这两种方式之前,需要先讲一下Instrumentation相关类和接口

java.lang.Instrumentation

Instrumentation

Instrumentation相关的类都在java.lang.Instrumentation包下,两个异常,两个接口,一个类。



两个异常在这里不做介绍,功能就像类名一样。核心的其实是Instrumentation接口,本文仅关注红框内的几个方法。这几个方法都是通过permain和agentmain获取到的instrumentation实例进行的操作。



从时间发展来看,其中jdk1.5开始支持的是下面几个方法,也就是说在jdk5的时候,仅支持添加和移除类转换器,且添加的类转换器只能在加载和重定义的时候使用。就是说如果类没有加载,那么通过addTransformer方法注册的ClassFileTransformer就可以对这个类进行增强,否则一旦类已经加载完毕,则只能通过redefineClasses,完全替换类定义再次触发loadClass来增强

addTransformer(ClassFileTransformer transformer)
removeTransformer(ClassFileTransformer transformer)
isRedefineClassesSupported();//依赖于MANIFEST中的Can-Redefine-Classes值
redefineClasses(ClassDefinition... definitions)

而从jdk1.6开始,增加了一个retransformClasses的概念。retransform和redefine的区别,前者是在原有类的基础上进行修改,后者则是完全重定义,不使用原有类做任何参考。

需要注意的事,只有在首次调用addTransformer时,将canRetransform设置为true的类,才可以被重新转换。

addTransformer(ClassFileTransformer transformer, boolean canRetransform);
isRetransformClassesSupported();
retransformClasses(Class<?>... classes)//依赖于MANIFEST中的Can-Retransform-Classes值
isModifiableClass(Class<?> theClass);

ClassFileTransformer、ClassDefinition

这两个类其实都是Instrumentation接口方法的入参,其中用的比较多的应该是ClassFileTransformer。这个类只有一个transform,jvm类加载的时候都会调用一遍这个方法。如果需要加强,那么就利用给定的参数,进行字节码的改动,将改动后的字节码作为返回值返回;如果无需增强,则直接返回null即可。

byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)

ClassDefinition也类似,不过是在对象里重新绑定class和byte的关系

public final class ClassDefinition {
/**
* The class to redefine
*/
private final Class<?> mClass; /**
* The replacement class file bytes
*/
private final byte[] mClassFile;

实践

MANIFEST.MF配置

在pom文件中添加下面的代码,根据需要修改参数值

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>
com.qisi.agent.InterviewByteButtyAgent
</Premain-Class>
<Agent-Class>
com.qisi.agent.InterviewByteButtyAgent
</Agent-Class>
<Can-Redefine-Classes>
true
</Can-Redefine-Classes>
<Can-Retransform-Classes>
true
</Can-Retransform-Classes>
<Built-By>
qisi
</Built-By>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>

-javaagent:

在这种方式下,起作用的是permain,也就是说-javaagent和permain方法是配套使用的。

核心就是添加一个自定义的ClassFileTransformer,可以另起一个类,也可以这样匿名类。

如果只是熟悉流程可以像下面一样,直接打印一些日志,不去修改类;

    public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println("enhance by premain,params:"+agentArgs);
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
System.out.println("premain load Class :" + className);
return classfileBuffer;
}
}, true);
}

如果要真实修改,需要引入asm javassist bytebuddy等修改字节码的框架。下面这部分就是使用了bytebuddy,作用是让任何类的testAgent方法,都返回固定值transformed

public static void premain(String agentArgs, Instrumentation instrumentation) throws ClassNotFoundException {
System.out.println("enhance by permain InterviewByteButtyAgent,params:"+agentArgs);
new AgentBuilder.Default().type(any()).transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
return builder.method(named("testAgent"))
.intercept(FixedValue.value("transformed"));
}
}).installOn(instrumentation);
}

编写完之后,就可以在任意项目添加一个存在testAgent方法的进行尝试了,比如

java -javaagent:/xxxx/path/agent-1.0-SNAPSHOT.jar=key1:value1,key2:value2 -jar AppDemo.jar

attach

agentmain

这种方式需要实现agentmain方法,和permian不太一样的地方是需要在addTransformer之后触发需要retransformClasses想要加强的类。

public static void agentmain(String agentArgs, Instrumentation instrumentation) {
System.out.println("enhance by agentmain,params:"+agentArgs);
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
System.out.println("agentmain load Class :" + className);
return classfileBuffer;
}
}, true);
try {
instrumentation.retransformClasses(Class.forName("com.qisi.mybatis.app.controller.FirstRequestController"));
} catch (UnmodifiableClassException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

同样,提供一个bytebuddy的例子,下面这个则是指定修改FirstRequestController的testAgent方法的返回值为transformed

public static void agentmain(String agentArgs, Instrumentation instrumentation) throws ClassNotFoundException {
System.out.println("enhance by agentmain InterviewByteButtyAgent,params:"+agentArgs);
//这里RedefinitionStrategy必须注意,默认的DISABLED是不支持retransform
new AgentBuilder.Default().with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).type(new AgentBuilder.RawMatcher() {
@Override
public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
return typeDescription.getName().contains("FirstRequestController");
}
}).transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
System.out.println("enhance"+typeDescription.getName());
return builder.method(named("testAgent"))
.intercept(FixedValue.value("transformed"));
}
//这里采用disableClassFormatChanges的方案,好像还可以使用advice
}).disableClassFormatChanges().installOn(instrumentation);
try {
instrumentation.retransformClasses(Class.forName("com.qisi.mybatis.app.controller.FirstRequestController"));
} catch (UnmodifiableClassException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

VirtualMachine

不同于-javaagent命令,这里需要使用自jdk6开始提供的VirtualMachine类,在tool.jar包里

下面的方法是我参考arthas写的一个attach的流程,选择我们想要attach的进程,然后加载我们上面写好的jar包就好了。

public class AgentTest {
public static void main(String[] args) throws IOException, AttachNotSupportedException {
String pid = null;
try {
Process jps = Runtime.getRuntime().exec("jps");
InputStream inputStream = jps.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
System.out.println("选择要attach的进程");
pid= new Scanner(System.in).nextLine();
System.out.println("选择的pid是"+pid);
} catch (IOException e) {
e.printStackTrace();
}
for (VirtualMachineDescriptor virtualMachineDescriptor : VirtualMachine.list()) {
if (virtualMachineDescriptor.id().equals(pid)){
VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor);
try {
attach.loadAgent("/xxxxx/agent/target/agent-1.0-SNAPSHOT.jar","参数1,参数2");
} catch (AgentLoadException e) {
e.printStackTrace();
} catch (AgentInitializationException e) {
e.printStackTrace();
} finally {
attach.detach();
}
break;
}
}
}
}

参考文档:

探秘 Java 热部署二(Java agent premain)

JAVA热更新1:Agent方式热更 | 花隐间-JAVA游戏技术解决方案

ByteBuddy入门教程

Java Agent技术的更多相关文章

  1. java agent技术原理及简单实现

    注:本文定义-在函数执行前后增加对应的逻辑的操作统称为MOCK 1.引子 在某天与QA同学进行沟通时,发现QA同学有针对某个方法调用时,有让该方法停止一段时间的需求,我对这部分的功能实现非常好奇,因此 ...

  2. Java探针-Java Agent技术-阿里面试题

    Java探针参考:Java探针技术在应用安全领域的新突破 最近面试阿里,面试官先是问我类加载的流程,然后问了个问题,能否在加载类的时候,对字节码进行修改 我懵逼了,答曰不知道,面试官说可以的,使用Ja ...

  3. 深入浅出Java探针技术1--基于java agent的字节码增强案例

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...

  4. Java 调式、热部署、JVM 背后的支持者 Java Agent

    我们平时写 Java Agent 的机会确实不多,也可以说几乎用不着.但其实我们一直在用它,而且接触的机会非常多.下面这些技术都使用了 Java Agent 技术,看一下你就知道为什么了. -各个 J ...

  5. Java动态追踪技术探究(动态修改)

    Java动态追踪技术探究 Java探针-Java Agent技术-阿里面试题 秒懂Java动态编程(Javassist研究) 可以用于在类加载的时候,修改字节码. Java agent(Java探针) ...

  6. [转] Java Agent使用详解

    以下文章来源于古时的风筝 ,作者古时的风筝 我们平时写 Java Agent 的机会确实不多,也可以说几乎用不着.但其实我们一直在用它,而且接触的机会非常多.下面这些技术都使用了 Java Agent ...

  7. Java 安全之Java Agent

    Java 安全之Java Agent 0x00 前言 在前面发现很多技术都会去采用Java Agent该技术去做实现,比分说RASP和内存马(其中一种方式).包括IDEA的这些破解都是基于Java A ...

  8. 大厂偏爱的Agent技术究竟是个啥

    搜索关注微信公众号"捉虫大师",后端技术分享,架构设计.性能优化.源码阅读.问题排查.踩坑实践. hello大家好,我是小楼,今天给大家分享一个关于Agent技术的话题,也是后端启 ...

  9. 记一次多个Java Agent同时使用的类增强冲突问题及分析

    摘要:Java Agent技术常被用于加载class文件之前进行拦截并修改字节码,以实现对Java应用的无侵入式增强. 本文分享自华为云社区<记一次多个JavaAgent同时使用的类增强冲突问题 ...

  10. Java Agent场景性能测试分析优化经验分享

    摘要:本文将以Sermant的SpringBoot 注册插件的性能测试及优化过程为例,分享在Java Agent场景如何进行更好的性能测试优化及在Java Agent下需要着重注意的性能陷阱. 作者: ...

随机推荐

  1. CF1833G Ksyusha and Chinchilla

    题目链接 题解 知识点:贪心,树形dp. 当 \(3 \not \mid n\) 时,显然无解. 考虑一种贪心策略,从叶子节点往上只,要以当前节点为根的子树大小能被 \(3\) 整除,就立刻切除这棵子 ...

  2. Spring Boot+Eureka+Spring Cloud微服务快速上手项目实战

    说明 我看了一些教程要么写的太入门.要么就是写的太抽象.真正好的文章应该是快速使人受益的而不是浪费时间.本文通过一个包括组织.部门.员工等服务交互的案例让刚接触spring cloud微服务的朋友快速 ...

  3. Vue+SpringBoot+ElementUI实战学生管理系统-6.院系管理模块

    1.章节介绍 前一篇介绍了用户管理模块,这一篇编写院系管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.实现效果 院系列表 修改院系 4.模块 ...

  4. Layui项目实战干货总结(精品)

    写代码时遇到的知识点拿出来分享. 1.layer弹出层显示在top顶层 // 监听工具条 table.on('tool(tb-book)', function (obj) { var data = o ...

  5. 解决主机ssh虚拟机linux慢的问题

    1.编辑sshd配置文件: vi /etc/ssh/sshd_config 找到行:#UseDNS yes 将注释去掉,把yes改为no 2.重启sshd服务: service sshd restar ...

  6. [BUUCTF][WEB][极客大挑战 2019]BabySQL 1

    靶机打开url 界面上显示,它做了更严格的过滤.看来后台是加了什么过滤逻辑 老规矩先尝试时候有sql注入的可能,密码框输入 123' 爆出sql错误信息,说明有注入点 构造万能密码注入 123' or ...

  7. queryset高级用法:prefetch_related

    这个方法和select_related方法类型,就是访问多个表中的数据的时候,减少查询的次数.这个方法是为了解决一对多和多对多的关系的查询问题.比如要获取标题中带有hello字符串的文章以及它的所有标 ...

  8. 2-Django之三板斧

    HttpResponse 返回字符串类型的数据 HttpResponse: 这是 Django 自带的类,用于构建基本的 HTTP 响应 我的app名称是demo,我们先按照正常的流程,在views中 ...

  9. Fasfdfs搭建

    目录 介绍 参考 tracker-server: storage-server: group: meta data: 部署 FastDfs服务架构图 本地部署服务 安装 libfastcommon 和 ...

  10. rt_snprintf()是什么

    在c++中snprintf()函数的解释 1,函数原型: int snprintf(char* dest_str,size_t size,const char* format,...); 2,功能 将 ...