PreRequisites
antlr4
asm-plugin: https://github.com/kamiiel/asm-intellij-plugin/releases
Intellij IDEA Install plugin from disk
排错记录
编译过程没问题,执行时报错:
Exception in thread “main” java.lang.ClassFormatError: Field “out” in class test has illegal signature “Ljava/io/PrintStream”
错误原因:在产生字节码的过程中,调用PrintStream忘记加分号
1 | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", |
应为:
1 | methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", |
JVM结构
1.类加载器
2.运行时数据区
3.执行引擎
4.本地方法库
double和long需要占据2个slot,其余基本类型和引用都是1个slot。
slot可以重复利用
垃圾回收(Garbage Collection)
查看Java进程使用的垃圾回收器:
查看默认垃圾回收器
JVM字节码
解析一个接口
接口定义:
1 | package edu.ustb.spesc.element; |
javap -v 结果:
1 | Classfile /home/wsd/STEM/Experiments/SPESC-Java-Compiler/compiler/target/test-classes/edu/ustb/spesc/element/InterfaceLook.class |
一些操作码助记符:除了比较容易记住的 i=int, l=long, s=short, b=byte, c=char, f=float, d=double,
还有 z=boolean, a=reference
DUP
这是一条直接操作操作数栈的指令,表示复制操作数栈栈顶的值,并插入到栈顶
比如最常见的:
1 | Object create() { |
编译后字节码如下:
1 | Method java.lang.Object create() |
第0行通过new创建了一个Object类型的对象,并将其引用压入栈顶
第3行复制栈顶的引用一份
第4行调用invokespecial为Object的构造函数,这里会消耗一个引用
所以第3行需要复制一份,不然调用invokespecial后会导致无法返回实例引用
解析一个类
类定义:
1 | package edu.ustb.spesc; |
javap -v 结果:
1 | Classfile /home/wsd/STEM/Experiments/SPESC-Java-Compiler/compiler/target/test-classes/edu/ustb/spesc/bytecodeTest.class |
由于JVM解析Java文件是按照行来解析的,一行Java代码可能对应多行字节码,所以设置 LineNumberTable 这个属性就是表示java文件的某行与字节码某行的对应关系。
比如上面的显示 line 3: 0 就表示bytecodeTest.java的第3行对应解析到jvm字节码的第0行即 aload_0
Slot是字节码中的存储单位,1个Slot大小为32bit(4 bytes)
使用ASM操作字节码
methodVisitor.visitMethodInsn五个参数
- INVOKE
- 调用函数的内部全类名
- 函数名
- 返回类型
- 是否接口调用
一些与标准文档的出入之处
3.1 .用作结构体访问的操作符,用做结束符会冲突
6.3 当事人描述中的 field 不止属于当事人,还可以作为合约的全局变量
另外,在filed定义中 attribute : (constant | type)
实际匹配过程应该是先匹配type,再匹配constant。否则如果出现类似 name : String 会将其匹配到constant
所以程序中使用 name : (type | value) 的方式匹配
name : IDENTIFIER
type : BOOLEAN (‘[‘ ‘]’)
| INTEGER (‘[‘ ‘]’) ;
value : NUMBER | STRING ;
其中,(‘[‘ ‘]’)*用于匹配数组
6.8 有关系符号,但缺少关系表达式
关系表达式属于谓词,可返回Yes,No,Unknown
关系表达式中可再分所属关系和比较关系,所属关系的关键词是belongsTo,比较关系的关键词有 > >= < <= == !=
所以在规则中添加了relationalExpression
实验探究 invokespecial 和 invokevirtual 的区别
1 | public class LibSayHello { |
bytecode如下:
1 | public sayHello()V |
1 | public class MainTest { |
bytecode如下:
1 | public static main([Ljava/lang/String;)V |
可以看到,init构造函数使用的是INVOKESPECIAL,普通方法sayHello使用的是INVOKEVIRTUAL
再来进行一层抽象,将LibHello写成接口的形式,类LibHelloImpl用于实现,此时测试main函数的bytecode变为如下:
1 | public static main([Ljava/lang/String;)V |
可以看到对sayHello的调用由原来的invokespecial变成了invokeinterface
假如LibHello是abstract类呢?
实验后可以发现,不管LibHelloImpl是否重写hello方法,对sayHello的调用都变回了invokevirtual
并且只要LibHello不变,即使改变了LibHelloImpl,也不会触发对于MainTest的字节码重新编译
一些思考
条款对应实现的Java数据结构是怎样的?一个函数、多个函数还是一个类
一个函数表达不出条款的意思
多个函数方案可以将when,while,where分别对应到一个函数,不过难以统一判断条款的参与方party,需要每个函数前面都加一个判断。
一个类的方案目前还没遇到问题。
条款中的动作参数是形参还是实参
使用形参parametersList。因为条款里的动作相当于AOP中的PointCut,定义为ActionSignature
与此区分,test函数里的action调用定义为 functionCall,使用实参argumentList
关于不支持函数重载
一般说来,虽然Action中支持定义参数,但是我们一般不使用同名不同参来进行函数重载(即不支持函数重载)
原因是我们使用的是方法名作为唯一标示去查找和判定的。为什么不用Method?
因为 Method 不支持 Berkeley DB持久化存储。
ContractEngine 的设计
根据Action是由自主触发还是AOP外部触发将工作模式分为自实现和AOP两种。
自实现 | AOP |
---|---|
handleAction触发,内部调用method.invoke执行动作 | Spring Bean中方法执行动作,@Pointcut拦截触发 |
两种模式对于duty的处理:
Duty | 自实现 | AOP |
---|---|---|
can | 通过调用handleAction触发 | 拦截外部调用后检查termObj, termObj.when 通过后执行 |
must | runMustActions 中触发调用must动作 | 和can对待是一样的。所以需要另写breach term处理must动作如果一直不触发的情况 |
cannot | handleAction中检查出来,则不发生动作执行 | termObj.when 通过后,则不执行pjp.proceed |
两种模式对于条件的处理:
Condition | 自实现 | AOP |
---|---|---|
when | termObj.when | 和自实现一样 |
while | termObj.while,和method.invoke 并列执行 | termObj.while,和 pjp.proceed 并列执行 |
where | termObj.where | 和自实现一样 |
通过学习JVM源码希望理解的一些问题
为什么静态方法不能被子类重写(Override)
评论
shortname
for Disqus. Please set it in_config.yml
.