JVM language development

开发基于JVM的声明式编程语言

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
2
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream");

应为:

1
2
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");

JVM结构

1.类加载器
2.运行时数据区
3.执行引擎
4.本地方法库

double和long需要占据2个slot,其余基本类型和引用都是1个slot。
slot可以重复利用

垃圾回收(Garbage Collection)

查看Java进程使用的垃圾回收器:
jps-jinfo.png

查看默认垃圾回收器
default_gc.png

JVM字节码

解析一个接口

接口定义:

1
2
3
4
package edu.ustb.spesc.element;

public interface InterfaceLook {
}

javap -v 结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Classfile /home/wsd/STEM/Experiments/SPESC-Java-Compiler/compiler/target/test-classes/edu/ustb/spesc/element/InterfaceLook.class
Last modified Apr 1, 2021; size 130 bytes
MD5 checksum 849d09ca95f1ed5733cca527dce9ed9c
Compiled from "InterfaceLook.java"
public interface edu.ustb.spesc.element.InterfaceLook
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #5 // edu/ustb/spesc/element/InterfaceLook
#2 = Class #6 // java/lang/Object
#3 = Utf8 SourceFile
#4 = Utf8 InterfaceLook.java
#5 = Utf8 edu/ustb/spesc/element/InterfaceLook
#6 = Utf8 java/lang/Object
{
}
SourceFile: "InterfaceLook.java"

一些操作码助记符:除了比较容易记住的 i=int, l=long, s=short, b=byte, c=char, f=float, d=double,
还有 z=boolean, a=reference

DUP

这是一条直接操作操作数栈的指令,表示复制操作数栈栈顶的值,并插入到栈顶
比如最常见的:

1
2
3
Object create() {
return new Object();
}

编译后字节码如下:

1
2
3
4
5
Method java.lang.Object create()
0 new #1 // Class java.lang.Object
3 dup
4 invokespecial #4 // Method java.lang.Object.<init>()V
7 areturn

第0行通过new创建了一个Object类型的对象,并将其引用压入栈顶
第3行复制栈顶的引用一份
第4行调用invokespecial为Object的构造函数,这里会消耗一个引用
所以第3行需要复制一份,不然调用invokespecial后会导致无法返回实例引用

解析一个类

类定义:

1
2
3
4
package edu.ustb.spesc;

public class bytecodeTest {
}

javap -v 结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Classfile /home/wsd/STEM/Experiments/SPESC-Java-Compiler/compiler/target/test-classes/edu/ustb/spesc/bytecodeTest.class
Last modified Apr 1, 2021; size 291 bytes
MD5 checksum 58439c875ed1b27cf7a5a68507efbf13
Compiled from "bytecodeTest.java"
public class edu.ustb.spesc.bytecodeTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#13 // java/lang/Object."<init>":()V
#2 = Class #14 // edu/ustb/spesc/bytecodeTest
#3 = Class #15 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Ledu/ustb/spesc/bytecodeTest;
#11 = Utf8 SourceFile
#12 = Utf8 bytecodeTest.java
#13 = NameAndType #4:#5 // "<init>":()V
#14 = Utf8 edu/ustb/spesc/bytecodeTest
#15 = Utf8 java/lang/Object
{
public edu.ustb.spesc.bytecodeTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ledu/ustb/spesc/bytecodeTest;
}
SourceFile: "bytecodeTest.java"

由于JVM解析Java文件是按照行来解析的,一行Java代码可能对应多行字节码,所以设置 LineNumberTable 这个属性就是表示java文件的某行与字节码某行的对应关系。
比如上面的显示 line 3: 0 就表示bytecodeTest.java的第3行对应解析到jvm字节码的第0行即 aload_0
Slot是字节码中的存储单位,1个Slot大小为32bit(4 bytes)

使用ASM操作字节码

methodVisitor.visitMethodInsn五个参数

  1. INVOKE
  2. 调用函数的内部全类名
  3. 函数名
  4. 返回类型
  5. 是否接口调用

一些与标准文档的出入之处

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
2
3
4
5
public class LibSayHello {
public void sayHello() {
System.out.println("Hello");
}
}

bytecode如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public sayHello()V
L0
LINENUMBER 5 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Hello"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE this Ledu/ustb/spesc/LibSayHello; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
1
2
3
4
5
6
public class MainTest {
public static void main(String[] args) {
LibSayHello testObj = new LibSayHello();
testObj.sayHello();
}
}

bytecode如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
NEW edu/ustb/spesc/LibSayHello
DUP
INVOKESPECIAL edu/ustb/spesc/LibSayHello.<init> ()V
ASTORE 1
L1
LINENUMBER 6 L1
ALOAD 1
INVOKEVIRTUAL edu/ustb/spesc/LibSayHello.sayHello ()V
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE testObj Ledu/ustb/spesc/LibSayHello; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2

可以看到,init构造函数使用的是INVOKESPECIAL,普通方法sayHello使用的是INVOKEVIRTUAL

再来进行一层抽象,将LibHello写成接口的形式,类LibHelloImpl用于实现,此时测试main函数的bytecode变为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
NEW edu/ustb/spesc/LibHelloImpl
DUP
INVOKESPECIAL edu/ustb/spesc/LibHelloImpl.<init> ()V
ASTORE 1
L1
LINENUMBER 6 L1
ALOAD 1
INVOKEINTERFACE edu/ustb/spesc/LibHello.sayHello ()V (itf)
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE testObj Ledu/ustb/spesc/LibHello; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2

可以看到对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)

设计模式 之 访问者模式 Spring cloud 总线实验

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×