为什么Spring技术很强大?(1)

目前,我就有一个实际项目的需求。我们的一个产品,需要一个规则引擎,解析自定义的DSL,进行规则的计算。这个规则引擎处理的数据量比较大,所以它的性能越高越好。因此,如果把 DSL 编译成字节码就最理想了。

既然字节码生成技术有很强的实用价值,那么本文就带你掌握它。

我会先带你了解 Java 的虚拟机和字节码的指令,然后借助 asm 这个工具,生成字节码,最后,再实现从 AST 编译成字节码。通过这样一个过程,你会加深对 Java 虚拟机的了解,掌握字节码生成技术,从而更加了解 Spring 的运行机制,甚至有能力编写这样的工具!

Java虚拟机和字节码

===========

字节码是一种二进制格式的中间代码,它不是物理机器的目标代码,而是运行在 Java 虚拟机上,可以被解释执行和即时编译执行。

在讲后端技术时,我强调的都是,如何生成直接在计算机上运行的二进制代码,这比较符合C、C++、Go 等静态编译型语言。但如果想要解释执行,除了直接解释执行 AST 以外,我没有讲其他解释执行技术。

而目前更常见的解释执行的语言,是采用虚拟机,其中最典型的就是 JVM,它能够解释执行 Java 字节码。

而虚拟机的设计又有两种技术:一是基于栈的虚拟机;二是基于寄存器的虚拟机

标准的 JVM 是基于栈的虚拟机(后面简称“栈机”)。

每一个线程都有一个 JVM 栈,每次调用一个方法都会生成一个栈桢,来支持这个方法的运行。栈桢里面又包含了本地变量数组(包括方法的参数和本地变量)、操作数栈和这个方法所用到的常数。

栈机是基于操作数栈做计算的。以“2+3”的计算为例,只要把它转化成逆波兰表达式,“2 3 +”,然后按照顺序执行就可以了。也就是:先把 2 入栈,再把 3 入栈,再执行加法指令,这时,要从栈里弹出 2 个操作数做加法计算,再把结果压入栈。

你可以看出,栈机的加法指令,是不需要带操作数的,就是简单的“iadd”就行,这跟你之前学过的 IR 都不一样。为什么呢?因为操作数都在栈里,加法操作需要 2 个操作数,从栈里弹出 2 个元素就行了。

也就是说,指令的操作数是由栈确定的,我们不需要为每个操作数显式地指定存储位置,所以指令可以比较短,这是栈机的一个优点

接下来,我们聊聊字节码的特点。

字节码是什么样子的呢?我编写了一个简单的类,其中的 foo() 方法实现了一个简单的加法计算,你可以看看它对应的字节码是怎样的:

publicclassMyClass{

publicintfoo(inta){

returna+3;

}

}

在命令行终端敲入下面两行命令,生成文本格式的字节码文件:

javacMyClass.java

javap-vMyClass>MyClass.bc

打开 MyClass.bc 文件,你会看到下面的内容片段:

publicintfoo(int);

Code:

0:iload_1 //把下标为1的本地变量入栈

1:iconst_3 //把常数3入栈

2:iadd //执行加法操作

3:ireturn //返回

其中,foo() 方法一共有四条指令,前三条指令是计算一个加法表达式 a+3。这完全是按照逆波兰表达式的顺序来执行的:先把一个本地变量入栈,再把常数 3 入栈,再执行加法运算。

如果你细心的话,应该会发现:把参数 a 入栈的第一条指令,用的下标是 1,而不是 0。这是因为,每个方法的第一个参数(下标为 0)是当前对象实例的引用(this)。

我提供了字节码中,一些常用的指令,增加你对字节码特点的直观认识,完整的指令集可以参见JVM 的规格书:

其中,每个指令都是 8 位的,占一个字节,而且 iload_0,iconst_0 这种指令,甚至把操作数(变量的下标、常数的值)压缩进了操作码里,可以看出,字节码的设计很注重节省空间。

根据这些指令所对应的操作码的数值,MyClass.bc 文件中,你所看到的那四行代码,变成二进制格式,就是下面的样子:

你可以用"hexdump MyClass.class"显示字节码文件的内容,从中可以发现这个片段(就是橙色框里的内容):

现在,你已经初步了解了基于栈的虚拟机,与此对应的是基于寄存器的虚拟机。这类虚拟机的运行机制跟机器码的运行机制是差不多的,它的指令要显式地指出操作数的位置(寄存器或内存地址)。它的优势是:可以更充分地利用寄存器来保存中间值,从而可以进行更多的优化。

例如,当存在公共子表达式时,这个表达式的计算结果可以保存在某个寄存器中,另一个用到该公共子表达式的指令,就可以直接访问这个寄存器,不用再计算了。在栈机里是做不到这样的优化的,所以基于寄存器的虚拟机,性能可以更高。而它的典型代表,是 Google公司为 Android 开发的 Dalvik 虚拟机和 Lua 语言的虚拟机。

这里你需要注意,栈机并不是不用寄存器,实际上,操作数栈是可以基于寄存器实现的,寄存器放不下的再溢出到内存里。只不过栈机的每条指令,只能操作栈顶部的几个操作数,所以也就没有办法访问其它寄存器,实现更多的优化。

现在,你应该对虚拟机以及字节码有了一定的了解了。那么,如何借助工具生成字节码呢?你可能会问了:为什么不纯手工生成字节码呢?当然可以,只不过借助工具会更快一些。

就像你生成 LLVM 的 IR 时,也曾获得了 LLVM 的 API 的帮助。所以,接下来我会带你认识 asm 这个工具,并借助它为我们生成字节码。

字节码生成工具asm

==========

其实,有很多工具会帮我们生成字节码,比如 Apache BCEL、Javassist 等,选择 asm 是因为它的性能比较高,并且它还被 Spring 等著名软件所采用。

asm是一个开源的字节码生成工具。Grovvy 语言就是用它来生成字节码的,它还能解析Java 编译后生成的字节码,从而进行修改。

asm 解析字节码的过程,有点像 xml 的解析器解析 xml 的过程:先解析类,再解析类的成员,比如类的成员变量(Field)、类的方法(Mothod)。在方法里,又可以解析出一行行的指令。

你需要掌握两个核心的类的用法:

  • ClassReader,用来解析字节码。

  • ClassWriter,用来生成字节码。

这两个类如果配合起来用,就可以一边读入,做一定修改后再写出,从而实现对原来代码的修改。

我们先试验一下,用 ClassWriter 生成字节码,看看能不能生成一个跟前面示例代码中的MyClass 一样的类(我们可以称呼这个类为 MyClass2),里面也有一个一模一样的 foo函数。相关代码参考genMyClass2()方法,这里只拿出其中一段看一下:

//创建foo方法

MethodVisitormv=cw.visitMethod(Opcodes.ACC_PUBLIC,“foo”,

“(I)I”,//括号中的是参数类型,括号后面的是返回值类型

null,null);

//添加参数a

mv.visitParameter(“a”,Opcodes.ACC_PUBLIC);

mv.visitVarInsn(Opcodes.ILOAD,1);

//iload_1

mv.visitInsn(Opcodes.ICONST_3);

//iconst_3

mv.visitInsn(Opcodes.IADD);

//iadd

mv.visitInsn(Opcodes.IRETURN);

//ireturn

//设置操作数栈最大的帧数,以及最大的本地变量数

mv.visitMaxs(2,2);

//结束方法

mv.visitEnd();

//创建foo方法

MethodVisitormv=cw.visitMethod(Opcodes.ACC_PUBLIC,“foo”,

“(I)I”,//括号中的是参数类型,括号后面的是返回值类型

null,null);

//添加参数a

mv.visitParameter(“a”,Opcodes.ACC_PUBLIC);

mv.visitVarInsn(Opcodes.ILOAD,1);

//iload_1

mv.visitInsn(Opcodes.ICONST_3);

//iconst_3

mv.visitInsn(Opcodes.IADD);

//iadd

mv.visitInsn(Opcodes.IRETURN);

//ireturn

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

[外链图片转存中…(img-qRlsoSwF-1713448835283)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值