前言

说道Javaagent是最近经常在使用这个技术,顺便了解了原理与根源,实际上就是jvm开个代理字节码修改的instrument接口。但实际上使用,根据使用的方式不同而略有区别。

1. Javaagent使用

实际上,笔者在前段时间写了arthas的启动原理(83条消息) arthas 启动原理分析_fenglllle的博客-CSDN博客,简单的说明了Javaagent的2种方式,jvm参数方式与动态attach。

以动态attach为例,实际上以jvm参数的agent类似,动态attach支持远程attach。

1.1 agent jar,demo

public class AgentMainDemo {private static synchronized void main(String args, Instrumentation inst) {try {System.out.println("agent exec ......");inst.addTransformer(new ClassFileTransformer() {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {//字节码修改,替换System.out.println("------ byte instead -----");return new byte[0];}}, true);Class<?> clazz = Class.forName("com.feng.agent.demo.ReTransformDemo");inst.retransformClasses(clazz);} catch (ClassNotFoundException | UnmodifiableClassException e) {e.printStackTrace();}}public static void premain(String args, Instrumentation inst) {main(args, inst);}public static void agentmain(String args, Instrumentation inst) {main(args, inst);}
}

pom打包manifest支持

            <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.feng.agent.demo.AgentMainDemo</Premain-Class><Agent-Class>com.feng.agent.demo.AgentMainDemo</Agent-Class><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></execution></executions></plugin>

1.2 运行的Java应用&tools.jar

public class DemoMain {public static void main(String[] args) throws InterruptedException {System.out.println("I'm a app");Thread.sleep(100000000000l);}
}

执行,可以debug执行都行。执行后pid笔者为 3041

tools.jar,需要载入才行

public class AttachMain {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {VirtualMachine machine = null;try {machine = VirtualMachine.attach("3041");machine.loadAgent("/Users/huahua/IdeaProjects/java-agent-demo/attach-demo/src/main/resources/agent-demo-jar-with-dependencies.jar");} finally {if (machine != null) {machine.detach();}}}
}

1.3 执行结果

可以看到agent exec 的字样,说明agent已经load了,且进行了字节码替换。实际上transform的ClassFileTransformer可以形成调用链,一个类可以被多次transform。transform默认是有

ClassFileTransformer的。

2. Javaagent原理

简单介绍Javaagent的原理:Javaagent分为jvm参数方式与动态attach方式

jvm参数方式:这种方式比较常用,因为可以通过启动参数内置

动态attach:这种方式比较灵活,可以多次attach,且可以销毁attach的agent。

实际上jvm加载逻辑差不多,这里以复杂的动态attach为例

关键还是:provider.attachVirtualMachine(id);

    public static VirtualMachine attach(String id)throws AttachNotSupportedException, IOException{if (id == null) {throw new NullPointerException("id cannot be null");}List<AttachProvider> providers = AttachProvider.providers();if (providers.size() == 0) {throw new AttachNotSupportedException("no providers installed");}AttachNotSupportedException lastExc = null;for (AttachProvider provider: providers) {try {return provider.attachVirtualMachine(id);} catch (AttachNotSupportedException x) {lastExc = x;}}throw lastExc;}

然后进一步跟踪:

可以看到使用了SPI技术,笔者Mac系统,如果是Linux或者win,这里是不同的

逻辑大同小异:

static {System.loadLibrary("attach");tmpdir = getTempDir();
}

先load c的lib,然后获取临时目录

    BsdVirtualMachine(AttachProvider provider, String vmid)throws AttachNotSupportedException, IOException{super(provider, vmid);// This provider only understands pidsint pid;try {pid = Integer.parseInt(vmid);} catch (NumberFormatException x) {throw new AttachNotSupportedException("Invalid process identifier");}//这段注释很明显,先找socket文件,找不到就创建attach文件,发送quit信号,再试查找socket文件// Find the socket file. // If not found then we attempt to start the attach mechanism in the target VM by sending it a QUIT signal.// Then we attempt to find the socket file again.//查找socket文件path = findSocketFile(pid);if (path == null) {File f = new File(tmpdir, ".attach_pid" + pid);//创建attach文件createAttachFile(f.getPath());try {//发送退出信号,启动attach mechanism连接途径sendQuitTo(pid);// give the target VM time to start the attach mechanismint i = 0;long delay = 200;int retries = (int)(attachTimeout() / delay);do {try {Thread.sleep(delay);} catch (InterruptedException x) { }//多次查找socket文件path = findSocketFile(pid);i++;} while (i <= retries && path == null);if (path == null) {throw new AttachNotSupportedException("Unable to open socket file: target process not responding " +"or HotSpot VM not loaded");}} finally {f.delete();}}// Check that the file owner/permission to avoid attaching to// bogus processcheckPermissions(path);// Check that we can connect to the process// - this ensures we throw the permission denied error now rather than// later when we attempt to enqueue a command.//socket创建int s = socket();try {//连接socket,相当于远程(另一个jvm进程)连上了pidconnect(s, path);} finally {close(s);}}// Return the socket file for the given process.// Checks temp directory for .java_pid<pid>.private String findSocketFile(int pid) {String fn = ".java_pid" + pid;File f = new File(tmpdir, fn);return f.exists() ? f.getPath() : null;}

建立socket连接,就进行下一步,loadjar

实际上这里就可以看到是要加载instrument。执行load指令,拿到结果,实际上load jar加载结束,agent就注入生效了,这个过程是JDK触发完成

    private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options)throws AgentLoadException, AgentInitializationException, IOException{InputStream in = execute("load",agentLibrary,isAbsolute ? "true" : "false",options);try {int result = readInt(in);if (result != 0) {throw new AgentInitializationException("Agent_OnAttach failed", result);}} finally {in.close();}}

继续load

    InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {assert args.length <= 3;                // includes null// did we detach?String p;synchronized (this) {if (this.path == null) {throw new IOException("Detached from target VM");}p = this.path;}// create UNIX socketint s = socket();// connect to target VMtry {connect(s, p);} catch (IOException x) {close(s);throw x;}IOException ioe = null;// connected - write request// <ver> <cmd> <args...>try {writeString(s, PROTOCOL_VERSION);writeString(s, cmd);for (int i=0; i<3; i++) {if (i < args.length && args[i] != null) {//把jar的路径写给JVM,就结束了,JVM指令执行load指令writeString(s, (String)args[i]);} else {writeString(s, "");}}} catch (IOException x) {ioe = x;}// Create an input stream to read replySocketInputStream sis = new SocketInputStream(s);// Read the command completion statusint completionStatus;try {completionStatus = readInt(sis);} catch (IOException x) {sis.close();if (ioe != null) {throw ioe;} else {throw x;}}if (completionStatus != 0) {sis.close();// In the event of a protocol mismatch then the target VM// returns a known error so that we can throw a reasonable// error.if (completionStatus == ATTACH_ERROR_BADVERSION) {throw new IOException("Protocol mismatch with target VM");}// Special-case the "load" command so that the right exception is// thrown.if (cmd.equals("load")) {throw new AgentLoadException("Failed to load agent library");} else {throw new IOException("Command failed in target VM");}}// Return the input stream so that the command output can be readreturn sis;}

jdk里面如何执行的呢,打开OpenJDK InvocationAdapter.c,jvm参数加载的agent执行

Agent_OnLoad函数

而动态attach的agent,执行

Agent_OnAttach函数

之所以读取manifest文件是jdk定义的,这个是动态attach,读取Agent-Class,另外还有 boot-class-path

下面才是核心

3部曲

1. 创建InstrumentationImpl实例

2. 打开ClassFileLoadHook,这个与字节码替换回调相关

3. 启动agent,实际上是调用第一步创建InstrumentationImpl实例的loadClassAndCallAgentmain方法

    private void loadClassAndCallPremain(String var1, String var2) throws Throwable {this.loadClassAndStartAgent(var1, "premain", var2);}private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {this.loadClassAndStartAgent(var1, "agentmain", var2);}

另一个方法就是jvm参数方式的调用函数

3. idea debug

之所以说idea的debug能力是笔者在使用jmx技术时,发现

javaagent技术原理相关推荐

  1. JVM SandBox 的技术原理与应用分析

    原文:https://www.infoq.cn/article/TSY4lGjvSfwEuXEBW*Gp 一.前言 在开始之前,我们先来模拟一下以下的场景: 小李:"小明,你的接口没有返回数 ...

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

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

  3. php 伪静态 page-18.html,PHP 伪静态实现技术原理讲解

    PHP 伪静态实现技术原理讲解 发布于 2015-01-18 23:52:58 | 129 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hypertext Pre ...

  4. 看了极光推送技术原理的几点思考

    看了极光推送技术原理的几点思考 分类: android2012-11-26 20:50 16586人阅读 评论(18) 收藏 举报 目录(?)[+] 移动互联网应用现状 因为手机平台本身.电量.网络流 ...

  5. 秋色园QBlog技术原理解析:性能优化篇:用户和文章计数器方案(十七)

    2019独角兽企业重金招聘Python工程师标准>>> 上节概要: 上节 秋色园QBlog技术原理解析:性能优化篇:access的并发极限及分库分散并发方案(十六)  中, 介绍了 ...

  6. 详解微信域名防封的方法以及检测等工具的技术原理

    微信域名完全防封是绝对不可能的,这是必须明确的,曾经有人打折<不死域名>的概念,它不是不死,是稍微命长一点,在推广上成本更低一下,效果更好一些, 主要的技术原理是利用了腾讯云的域名安全联盟 ...

  7. NLP实践:对话系统技术原理和应用

    本文节选自电子工业出版社<自然语言处理实践:聊天机器人技术原理与应用> 作者:王昊奋&邵浩&李方圆&张凯&宋亚楠 以下是节选内容 按照技术实现,我们可将任务 ...

  8. 范成法加工matlab_光学非球面技术原理与加工技术

    光学非球面的定义 广义来说,非球面是不包括球面和平面的其他表面.从应用的角度来说,非球面可以分成轴对称的非球面.具有两个对称面的非球面.没有对称性的自由曲面. 非球面分类 通常把非球面分成二次非球面和 ...

  9. 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五)...

    文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色 ...

最新文章

  1. 解决:无法创建该DNS 服务器的委派
  2. Wigner-Ville分布算法的C++实现
  3. 三位数的茎叶图怎么看_贝德玛化妆品生产日期怎么看?贝德玛化妆品保质期怎么看?...
  4. Unity脚本生命周期与执行顺序
  5. SVGA转html最快方法(使用Python直接转)
  6. 数据库MySql阶段总结
  7. 求教:.Net Framework 3.5 SP1安装失败
  8. 一步步的教你安装UChome (UChome 安装教程)
  9. 去除html中的font标签的正则表达式
  10. HNSWNSG-基于图的ANN近邻搜索
  11. 如何实现罗克韦尔PLC的模拟量采集和远程上下载?
  12. 三维形体的数据结构(1)半边数据结构
  13. 前缀和的n个神奇操作
  14. WRMPS经典Cookie欺骗漏洞批量拿下shell-黑客博客
  15. 电商翻身受宠社交成大势 2019上市科技股大盘点
  16. dockers安装redis
  17. 关于Deepin商店没有应用的解决办法
  18. 电容麦克风的幻象供电
  19. 计算机班级组织什么活动,【班级活动】计算机科学与技术3班开展“三月春游”户外活动...
  20. 有种速度让你望尘莫及 | 手机QQ及Qzone速度优化实践

热门文章

  1. 服务器性能测试,导热膏选择
  2. 微商怎么做推广?18个微商推广技巧告诉你
  3. js深拷贝和浅拷贝的区别
  4. QQ浏览器推小程序 微信小程序三步完成接入
  5. qtextedit固定内容_QTextEdit选中某行文本
  6. 任正非鸿蒙不是手机系统,任正非称华为鸿蒙暂无计划用于手机 2019华为开发者大会什么时间召开?有何看点?...
  7. h5+实现手机端的录音,拍照,录像
  8. LATEX图片(子图)排版
  9. [Linux-进程控制] 进程创建进程终止进程等待进程程序替换简易shell
  10. BPR贝叶斯个性化排序