一勺思想

We are all in the gutter, but some of us are looking at the stars. (我们都生活在阴沟里,但仍有人仰望星空 )- 王尔德 《温德米尔夫人的扇子》

举世混浊我独清,众人皆醉我独醒 - 屈原 《楚辞》

前言

ASM是一种通用Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused onperformance. Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).

本篇文章分享的是对ASM的理解和应用,之前需要我们掌握class字节码JVM基于栈的设计模式,JVM指令

class字节码

我们编写的java文件,会通过javac命令编译为class文件,JVM最终会执行该类型文件来运行程序。下图所示为class文件结构(图片来源网络)。

下面我们通过一个简单的实例来进行说明。下面是我们编写的一个简单的java文件,只是简单的函数调用.

public class Test {private int num1 = 1;public static int NUM1 = 100;public int func(int a,int b){return add(a,b);}public int add(int a,int b) {return a+b+num1;}public int sub(int a, int b) {return a-b-NUM1;}
}

使用javac -g Test.java编译为class文件,然后通过 javap -verbose Test.class 命令查看class文件格式。

public class com.wuba.asmdemo.Testminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#26         // java/lang/Object."<init>":()V#2 = Fieldref           #5.#27         // com/wuba/asmdemo/Test.num1:I#3 = Methodref          #5.#28         // com/wuba/asmdemo/Test.add:(II)I#4 = Fieldref           #5.#29         // com/wuba/asmdemo/Test.NUM1:I#5 = Class              #30            // com/wuba/asmdemo/Test#6 = Class              #31            // java/lang/Object#7 = Utf8               num1#8 = Utf8               I#9 = Utf8               NUM1#10 = Utf8               <init>#11 = Utf8               ()V#12 = Utf8               Code#13 = Utf8               LineNumberTable#14 = Utf8               LocalVariableTable#15 = Utf8               this#16 = Utf8               Lcom/wuba/asmdemo/Test;#17 = Utf8               func#18 = Utf8               (II)I#19 = Utf8               a#20 = Utf8               b#21 = Utf8               add#22 = Utf8               sub#23 = Utf8               <clinit>#24 = Utf8               SourceFile#25 = Utf8               Test.java#26 = NameAndType        #10:#11        // "<init>":()V#27 = NameAndType        #7:#8          // num1:I#28 = NameAndType        #21:#18        // add:(II)I#29 = NameAndType        #9:#8          // NUM1:I#30 = Utf8               com/wuba/asmdemo/Test#31 = Utf8               java/lang/Object
{public static int NUM1;descriptor: Iflags: ACC_PUBLIC, ACC_STATICpublic com.wuba.asmdemo.Test();     //构造函数descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield      #2                  // Field num1:I9: returnLineNumberTable:line 3: 0line 5: 4LocalVariableTable:Start  Length  Slot  Name   Signature0      10     0  this   Lcom/wuba/asmdemo/Test;public int func(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=3, locals=3, args_size=30: aload_01: iload_12: iload_23: invokevirtual #3                  // Method add:(II)I6: ireturnLineNumberTable:line 10: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       7     0  this   Lcom/wuba/asmdemo/Test;0       7     1     a   I0       7     2     b   Ipublic int add(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_11: iload_22: iadd3: aload_04: getfield      #2                  // Field num1:I7: iadd8: ireturnLineNumberTable:line 14: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  this   Lcom/wuba/asmdemo/Test;0       9     1     a   I0       9     2     b   Ipublic int sub(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_11: iload_22: isub3: getstatic     #4                  // Field NUM1:I6: isub7: ireturnLineNumberTable:line 18: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       8     0  this   Lcom/wuba/asmdemo/Test;0       8     1     a   I0       8     2     b   Istatic {};descriptor: ()Vflags: ACC_STATICCode:stack=1, locals=0, args_size=00: bipush        1002: putstatic     #4                  // Field NUM1:I5: returnLineNumberTable:line 7: 0
}
SourceFile: "Test.java"

可以看出在编译为class文件后,字段名称,方法名称,类型名称等均在常量池中存在的。从而做到减小文件的目的。同时方法定义也转变为了jvm指令。下面我们需要对jvm指令加深一下了解。在了解之前需要我们理解JVM基于栈的设计模式

JVM基于栈的设计模式

JVM的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性。在线程中执行一个方法时,我们会创建一个栈帧入栈并执行,如果该方法又调用另一个方法时会再次创建新的栈帧然后入栈,方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧,随后虚拟机将会丢弃此栈帧。

局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。虚拟机通过索引定位的方法查找相应的局部变量。举个例子。以上述的代码为例

 public int sub(int a, int b) {return a-b-NUM1;}

这个方法大家可以猜测一下局部变量有哪些? 答案是3个,不应该只有a,b吗?还有this,对应实例对象方法编译器都会追加一个this参数。如果该方法为静态方法则为2个了。

public int sub(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_11: iload_22: isub3: getstatic     #4                  // Field NUM1:I6: isub7: ireturnLineNumberTable:line 18: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       8     0  this   Lcom/wuba/asmdemo/Test;0       8     1     a   I0       8     2     b   I

所以局部变量表第0个元素为this, 第一个为a,第二个为b

操作数栈

通过局部变量表我们有了要操作和待更新的数据,我们如果对局部变量这些数据进行操作呢?通过操作数栈。当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。

JVM指令

  • load 命令:用于将局部变量表的指定位置的相应类型变量加载到操作数栈顶;
  • store命令:用于将操作数栈顶的相应类型数据保入局部变量表的指定位置;
  • invokevirtual:调用实例方法
  • ireturn: 当前方法返回int

在举个例子

a = b + c 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示

ASM操作

通过上面的介绍,我们对字节码和JVM指令有了进一步的了解,下面我们看一下ASM是如果编辑class字节码的。

ASM API

ASM API基于访问者模式,为我们提供了ClassVisitor,MethodVisitor,FieldVisitor API接口,每当ASM扫描到类字段是会回调visitField方法,扫描到类方法是会回调MethodVisitor,下面我们看一下API接口

ClassVisitor方法解析

public abstract class ClassVisitor {......public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);//访问类字段时回调public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);//访问类方法是回调public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);public void visitEnd();
}

MethodVisitor方法解析

public abstract class MethodVisitor {......public void visitParameter(String name, int access);//访问本地变量类型指令 操作码可以是LOAD,STORE,RET中一种;public void visitIntInsn(int opcode, int operand);//域操作指令,用来加载或者存储对象的Fieldpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor);//访问方法操作指令public void visitMethodInsn(int opcode, String owner, String name, String descriptor);public void visitEnd();
}

ASM 使用Demo

java源码

 public int add(int a,int b) {return a+b+num1;}

class字节码

 public int add(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_11: iload_22: iadd3: aload_04: getfield      #2                  // Field num1:I7: iadd8: ireturnLineNumberTable:line 14: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  this   Lcom/wuba/asmdemo/Test;0       9     1     a   I0       9     2     b   I

ASM对应的API

            mv = cw.visitMethod(ACC_PUBLIC, "add", "(II)I", null, null);mv.visitCode();mv.visitVarInsn(ILOAD, 1);mv.visitVarInsn(ILOAD, 2);mv.visitInsn(IADD);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "com/wuba/asmdemo/Test", "num1", "I");mv.visitInsn(IADD);mv.visitInsn(IRETURN);Label l1 = new Label();mv.visitLabel(l1);mv.visitLocalVariable("this", "Lcom/wuba/asmdemo/Test;", null, l0, l1, 0);mv.visitLocalVariable("a", "I", null, l0, l1, 1);mv.visitLocalVariable("b", "I", null, l0, l1, 2);mv.visitMaxs(2, 3);mv.visitEnd();

可以看出ASM是在指令层次上操作字节码的,和class字节码更加接近。如果我们有些字节码操作的需求,ASM一定可以实现的。只是使用起来比较麻烦一些。这里强烈推荐一款ASM插件ASM ByteCode Outline。 可以一键生成对应的ASM API代码

参考

Lyon:Java虚拟机—栈帧、操作数栈和局部变量表

字节码增强技术探索

ASM

https://www.kancloud.cn/alex_wsc/android_plugin/471061

JVM 栈帧之操作数栈与局部变量表

Jvm系列3-字节码指令 - Gityuan博客 | 袁辉辉的技术博客

写给 Android 开发者的 Gradle 系列(四)plugin 实战包体积瘦身

https://www.jianshu.com/p/d8c2ada6e

史上最通俗易懂的ASM教程相关推荐

  1. 【ASM】史上最通俗易懂的ASM教程 ASM 插件

    1.概述 转载:史上最通俗易懂的ASM教程 一勺思想 We are all in the gutter, but some of us are looking at the stars. (我们都生活 ...

  2. 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)

    最新Finchley版本请访问: https://www.fangzhipeng.com/springcloud/2018/08/30/sc-f1-eureka/ 或者 http://blog.csd ...

  3. 史上最简单的SpringCloud教程 | 第十篇: 高可用的服务注册中心

    转自:https://blog.csdn.net/forezp/article/details/81041101 文章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eu ...

  4. 史上最简单的SpringCloud教程 | 第六篇: 分布式配置中心(Spring Cloud Config)

    转:https://blog.csdn.net/forezp/article/details/70037291 最新版本: 史上最简单的SpringCloud教程 | 第六篇: 分布式配置中心(Spr ...

  5. 史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)

    转:https://blog.csdn.net/forezp/article/details/69939114 最新版本: 史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)( ...

  6. 史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix)

    转:https://blog.csdn.net/forezp/article/details/69934399 最新版本: 史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix) ...

  7. 史上最简单的SpringCloud教程 | 第三篇: 服务消费者(Feign)

    转:https://blog.csdn.net/forezp/article/details/69808079 最新版本: 史上最简单的SpringCloud教程 | 第三篇: 服务消费者(Feign ...

  8. Python爬虫人工智能大数据全栈视频史上最全合辑教程分享!

    Python爬虫人工智能大数据全栈视频史上最全合辑教程分享! 毫无疑问Python是这两年最火的编程语言,不仅容易上手,且在多个行业都可应用.尤其今年人工智能及大数据的发展,Python将会展现更多的 ...

  9. 史上最简单的git教程搭配Github和Gitee一起食用更佳

    史上最简单的git教程 开始之前 git的最简单使用 1. 安装 2. 配置 2.1 用户信息 3. 最基本使用 Github 1. 首先你需要一个账号 2. 你需要一个仓库 Gitee 开始之前 g ...

最新文章

  1. 秒杀系统架构解密与防刷设计 - 高可用架构系列
  2. ASP.NET MVC 上传文件
  3. 计算机二级c语言考点分析,计算机二级C语言考点分析.doc
  4. eclipse 导入maven项目_手把手的Spring Boot Web 项目教程,Hello Spring Boot
  5. c标签foreach遍历list_遍历 Dictionary,你会几种方式?
  6. Netty技术细节源码分析-MpscLinkedQueue队列原理分析
  7. Concepts in Games Development(游戏开发概述) 公开课笔记
  8. 【实习之T100开发】T100 P处理开发流程
  9. 使用tcpdump抓取HTTP包
  10. JMeter压力测试步骤
  11. 计算机类sci中接受综述么,sci综述类期刊有哪些
  12. 用python爬取隐藏内容_人民日报点赞北大保安小哥,自学Python后,人生开挂了!...
  13. 增强型for循环与Map集合的遍历
  14. ChatGPT版Office(Word/Excel/PPT)来了
  15. Java小游戏学习笔记
  16. 【转】从一个App跳转到另一APP
  17. 计算机领域中的CAE,什么是CAE?
  18. Tacotron以及Tacotron2详解
  19. 记一个微商城促销方案实现流程图
  20. 数据分析的目的、方法、思路

热门文章

  1. 狸猫哥哥和他的冬葵花
  2. 知识汇总二(简单光照模型)
  3. isotope自动布局
  4. 金蝶EAS客户端自定义菜单脚本导出
  5. Oracle导出报错: unknown command beginning “exp SCOTT/...“ - rest of line ignored.
  6. latex 中文书籍常见命令
  7. Testlink解决大用例导入问题
  8. Idea导包正确还是显示标红(错误)
  9. TERMIN汤铭 FE1.1四端口USB2.0芯片
  10. [运维] 在CentOS7系统上安装部署wok