jvm-编译
编译格式例子
如下面的代码:
void dspin(){
double j;
for (j = 0.0; j < 100.0; j++) {
;
}
}
javap反编译之后得到:
dspin()V
L0
LINENUMBER 20 L0
DCONST_0 //定义double常量0
DSTORE 1 //存储到本地变量表1
L1
FRAME APPEND [D]
DLOAD 1 //加载本地变量表1的值
LDC 100.0 //加载常量100.0
DCMPG //栈顶下的元素和栈顶元素作比较
IFGE L2 //如果大于等于,跳到L2结束循环。
DLOAD 1
DCONST_1
DADD //栈顶的两个元素相加
DSTORE 1
GOTO L1
L2
LINENUMBER 23 L2
FRAME SAME
RETURN
取常量
iconst_1:取常量1。 iconst_m1:取常量-1。
访问运行时常量池
访问常量池的指令有ldc,ldc_w,ldc2w。
ldc和ldc_w用于访问除long和double外的常量类型,如int,String。ldc_w在有大量的运行时常量池项目并且需要一个大的索引访问项目
时使用。ldc2_w用于访问所有的double和long类型。
byte,char,short作为小整型值,可能使用bipush,sipush或iconst指令。
类实例
创建一个实例的语句如下:
new TestFoo();
编译代码:
NEW net/zhaoyu/javapros/test/TestFoo
DUP
INVOKESPECIAL net/zhaoyu/javapros/test/TestFoo.<init> ()V
从上面的代码可以看出,java中new一个对象,需要执行3个步骤:1新建一个对象,2复制到栈顶,3调用构造方法,即init方法。
数组
有如下操作数组的代码:
void createBuffer() {
int buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}
编译后如下:
Method void createBuffer()
0 bipush 100
2 istore_2
3 bipush 12
5 istore_3
6 iload_2
7 newarray int // 创建数组,根据栈顶元素
9 astore_1 // 保存数组到本地变量
10 aload_1 // 数组入栈
11 bipush 10 // 常量10入栈
13 iload_3 // value变量入栈
14 iastore // 将栈顶值保存为数组[10]
15 aload_1 // 数组重新入栈
16 bipush 11 // 常量11入栈
18 iaload // 将数组[11]入栈
19 istore_3
20 return
操作栈中的操作
假如有如下代码:
long l=2;
public long nextIndex(){
return l++;
}
编译结果如下:
ALOAD 0 //this入栈
DUP //复制栈顶元素并入栈,栈为this,this
GETFIELD net/zhaoyu/javapros/test/TestFoo.l : J //消耗一个this,获取l,此时栈为this,l。
DUP2_X1 //复制栈顶的l,并插入到this下面。此时栈为j,this,j。
LCONST_1
LADD // 栈顶2个元素相加,栈为j,this,j+1
PUTFIELD net/zhaoyu/javapros/test/TestFoo.l : J //j+1赋值给字段。即this.j=j+1。结束后栈为j
LRETURN // 返回栈顶元素j。
DUP2_X1
可以理解为:将栈顶的2个Slot的值复制并插入到栈顶的3(2+1)个Slot的值下面,long和double占用2个slot,其他类型占用1个slot。
注意,JVM从不允许操作栈的操作指令修改和破坏操作栈中的变量,所以需要dup等操作。
throw和异常捕获
有如下代码:
void tryCatchTest(){
try {
nextIndex();
} catch (Exception e) {
throw new IllegalArgumentException();
}
}
编译后的代码如下:
void tryCatchTest();
Code:
0: aload_0
1: invokevirtual #5 // Method nextIndex:()J
4: pop2
5: goto 17 //try执行完,跳到return
8: astore_1 // <catch块> 发生throw,则存储Exception
9: new #7 // new IllegalArgumentException
12: dup
13: invokespecial #8 // IllegalArgumentException."<init>":()V
16: athrow // <catch块>结束
17: return
Exception table:
from to target type
0 5 8 Class java/lang/Exception
在上面的编译代码中,异常的tryCatch会生成Exception table。异常表的[from-to)记录try的范围,target是catch的行数。
finally的处理有些特殊,finally会将代码分布到try块和catch块中。以确保一定执行。例如有代码如下:
void tryCatchTest(){
try {
nextIndex();
} catch (Exception e) {
throw new IllegalArgumentException();
}finally {
throw new RuntimeException();
}
}
编译后的代码如下:
void tryCatchTest();
descriptor: ()V
flags:
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: invokevirtual #5 // Method nextIndex:()J
4: pop2
5: new #6 // <final块> class java/lang/RuntimeException
8: dup
9: invokespecial #7 // <final块> Method java/lang/RuntimeException."<init>":()V
12: athrow
13: astore_1 //<catch块>开始
14: new #9 // class java/lang/IllegalArgumentException
17: dup
18: invokespecial #10 // Method java/lang/IllegalArgumentException."<init>":()V
21: athrow
22: astore_2
23: new #6 // <final块> class java/lang/RuntimeException
26: dup
27: invokespecial #7 // <final块> Method java/lang/RuntimeException."<init>":()V
30: athrow //<catch块>结束
Exception table:
from to target type
0 5 13 Class java/lang/Exception
0 5 22 any
13 23 22 any
从编译代码中可以看出,finally语句会分别插入到try块和catch块中。并在异常表中插入两行。这两行用于捕获插入到try块和catch块中 的final块的异常。
同步
同步有两种实现方式,一种是在方法上隐式地加上ACC_SYNCHRONIZED
。另一种是使用monitorenter
和monitorexit
。
monitorenter
和monitorexit
指令会增加同步语句的复杂性,例如有如下程序:
void onlyMe(Foo f) {
synchronized(f) {
doSomething();
}
}
编译后的代码如下:
0 aload_1 // Push f
1 dup //
2 astore_2 //
3 monitorenter // 进入monitor,同步开始
4 aload_0 // Holding the monitor, pass this and...
5 invokevirtual #5 //
8 aload_2 // Push local variable 2 (f)
9 monitorexit // 从f的monitor中退出。
10 goto 18 // goto return
13 astore_3 // 异常情况处理
14 aload_2 // Push local variable 2 (f)
15 monitorexit // 确定异常情况下退出monitor
16 aload_3
17 athrow
18 return // Return
Exception table:
From To Target Type
4 10 13 any
13 16 13 any
从上面的代码可以看出,程序自动为synchronized块添加了异常表处理。用于处理synchronized块,及异常后的代码块。