<Inside JVM>是市面上少数几本系统介绍JVM的书籍,(作者Bill Venners的网站
artima也是非常有趣的Java技术社区),这本书最有意思的地方可能就是作者为了演示JVM内部工作原理编写的
Applet, 非常值得向大家推荐。<Inside JVM>最新的是第二版,也有些年头了,尽管JVM规范本身的变化并不大,但是java编译器的有些细节处理还是跟书里面不一样了,比如说现在的java编译器一般都会对finally作inline处理。
这个 Applet演示的是下面这段程序的bytecode。
代码1:
其主要目的演示的是finally子句的处理, 在Inside JVM和JVM Spec的7.13节当中,finally都是通过jsr和ret指令实现的,按照书中的说法,hopAround()方法将被编译成47个jvm指令,如下所示:
代码2:
但是如果用Sun JDK 1.4之后的编译器编译,这段程序实际上被编译成截然不同的38个jvm指令(如下所示),其主要区别是finally 子句被inline到了正常代码和exception handling代码,避免了jsr/ret, 从而减少了JSR跳转/pop出栈/ret返回三条指令,在本例中,不但提高了运行效率,还减少了class文件的大小,其代价是finally block之中的内容被复制到了两个地方,如果finally block的指令很多,class文件可能会变大。
代码3:
Eclipse编译器显然也将finally inline了,Eclipse 3.2甚至disable了编译器不inline finally的选项,ECJ编译出来的hopAround()由39条指令组成:
代码4:
区别不大,Eclipse编译器将exception handling的代码放在了正常路径的finally block之前,多出来的一句是第39句goto 2, 比较匪夷所思, 因为这一句显然是无法被执行到的。还有一个区别是exception table缩减为两项,少掉的两条是针对exception handling代码自身的,Sun javac加上的这两条意欲何为也有点让人迷惑...也许因为这个原因,ECJ编译出来的class要稍微小一些(对于这个class, 222bytes vs. 235bytes)。
这两种主流Java编译器都对finally做了inline处理,所以我们在平常写java代码的时候应该注意在try/catch/finally里面不要做太多的分支,并且finally block中包含的内容尽量不要太多,否则class文件可能会多占用不少空间。
还有一个tips: javac和Eclipse编译器缺省都带有debug信息,加上-g:none选项之后,class文件要小一半,对于Eclipse编译器尤其明显。
最后一个tips送给有耐心读到这里的人: 请代码3中的14-17行,在进入finally block之前,JVM将把变量i当前的值存入另外一个本地变量(istore_1),并且在26-27行将此值返回,所以如果在finally block中修改了i的值,对返回值是没有影响的,如果要将修改过的i返回,请在finally中直接return i。更多finally实现的细节请参考Inside JVM:).
这个 Applet演示的是下面这段程序的bytecode。
代码1:
/*
*copiedfrom http://www.artima.com/insidejvm/applets/HopAround.html
*/
class Clown...{
static int hopAround()...{
int i = 0 ;
while ( true )...{
try ...{
try ...{
i = 1 ;
}
finally ...{ // Thefirstfinallyclause
i = 2 ;
}
i = 3 ;
// Thisreturnnevercompletes,becauseof
// thecontinueinthesecondfinallyclause
return i;
}
finally ...{ // Thesecondfinallyclause
if (i == 3 )...{
// Thiscontinueoverridesthereturnstatement
continue ;
}
}
}
}
}
*copiedfrom http://www.artima.com/insidejvm/applets/HopAround.html
*/
class Clown...{
static int hopAround()...{
int i = 0 ;
while ( true )...{
try ...{
try ...{
i = 1 ;
}
finally ...{ // Thefirstfinallyclause
i = 2 ;
}
i = 3 ;
// Thisreturnnevercompletes,becauseof
// thecontinueinthesecondfinallyclause
return i;
}
finally ...{ // Thesecondfinallyclause
if (i == 3 )...{
// Thiscontinueoverridesthereturnstatement
continue ;
}
}
}
}
}
其主要目的演示的是finally子句的处理, 在Inside JVM和JVM Spec的7.13节当中,finally都是通过jsr和ret指令实现的,按照书中的说法,hopAround()方法将被编译成47个jvm指令,如下所示:
代码2:
/**/
/*
*copiedfromhttp://www.artima.com/insidejvm/applets/HopAround.html
*/
0 iconst_0 // Pushconstant0
1 istore_0 // Popintolocalvar0:inti=0;
// Bothtryblocksstarthere(seeexceptiontable,below):
2 iconst_1 // Pushconstant1
3 istore_0 // Popintolocalvar0:i=1;
4 jsr 18 // Jumptomini-subroutineatoffset18(the
// firstfinallyclause)
7 goto 24 // Jumptooffset24(tojustbelowfirst
// finallyclause)
// Catchclauseforthefirstfinallyclause:
10 astore 4 // Popthereferencetothrownexception,store
// inlocalvariable4
12 jsr 18 // Jumptomini-subroutineatoffset18(the
// firstfinallyclause)
15 aload 4 // Pushthereference(tothrownexception)
// fromlocalvariable4
17 athrow // Rethrowthesameexception
// Thefirstfinallyclause:
18 astore 5 // Storethereturnaddressinlocalvariable5
20 iconst_2 // Pushconstant2
21 istore_0 // Popintolocalvar0:i=2;
22 ret 5 // Jumptoreturnaddressstoredinlocalvariable5
// Bytecodesforthecodejustafterthefirstfinallyclause:
24 iconst_3 // Pushconstant3
25 istore_0 // Popintolocalvar0:inti=3;
// Bytecodesforthereturnstatment:
26 iload_0 // Pushtheintfromlocal
// variable0(i,whichis3)
27 istore_1 // Popandstoretheintintolocal
// variable1(thereturnvalue,i)
28 jsr 39 // Jumptomini-subroutineatoffset39(the
// secondfinallyclause)
31 iload_1 // Pushtheintfromlocalvariable1(the
// returnvalue)
32 ireturn // Returntheintonthetopofthestack
// Catchclauseforthesecondfinallyclause:
33 astore_2 // Popthereferencetothrownexception,store
// inlocalvariable2
34 jsr 39 // Jumptomini-subroutineatoffset39(the
// secondfinallyclause)
37 aload_2 // Pushthereference(tothrownexception)
// fromlocalvariable2
38 athrow // Rethrowthesameexception
// Thesecondfinallyclause:
39 astore_3 // Storethereturnaddressinlocalvariable3
40 iload_0 // Pushtheintfromlocalvariable0(i)
41 iconst_3 // Pushconstant3
42 if_icmpeq 47 // Ifthetoptwointsonthestackareequal,jump
// tooffset47:if(i==3){
45 ret 3 // Jumptoreturnaddressstoredinlocalvariable3
47 goto 2 // Jumptooffset2(thetopofthewhile
// block):continue;
*copiedfromhttp://www.artima.com/insidejvm/applets/HopAround.html
*/
0 iconst_0 // Pushconstant0
1 istore_0 // Popintolocalvar0:inti=0;
// Bothtryblocksstarthere(seeexceptiontable,below):
2 iconst_1 // Pushconstant1
3 istore_0 // Popintolocalvar0:i=1;
4 jsr 18 // Jumptomini-subroutineatoffset18(the
// firstfinallyclause)
7 goto 24 // Jumptooffset24(tojustbelowfirst
// finallyclause)
// Catchclauseforthefirstfinallyclause:
10 astore 4 // Popthereferencetothrownexception,store
// inlocalvariable4
12 jsr 18 // Jumptomini-subroutineatoffset18(the
// firstfinallyclause)
15 aload 4 // Pushthereference(tothrownexception)
// fromlocalvariable4
17 athrow // Rethrowthesameexception
// Thefirstfinallyclause:
18 astore 5 // Storethereturnaddressinlocalvariable5
20 iconst_2 // Pushconstant2
21 istore_0 // Popintolocalvar0:i=2;
22 ret 5 // Jumptoreturnaddressstoredinlocalvariable5
// Bytecodesforthecodejustafterthefirstfinallyclause:
24 iconst_3 // Pushconstant3
25 istore_0 // Popintolocalvar0:inti=3;
// Bytecodesforthereturnstatment:
26 iload_0 // Pushtheintfromlocal
// variable0(i,whichis3)
27 istore_1 // Popandstoretheintintolocal
// variable1(thereturnvalue,i)
28 jsr 39 // Jumptomini-subroutineatoffset39(the
// secondfinallyclause)
31 iload_1 // Pushtheintfromlocalvariable1(the
// returnvalue)
32 ireturn // Returntheintonthetopofthestack
// Catchclauseforthesecondfinallyclause:
33 astore_2 // Popthereferencetothrownexception,store
// inlocalvariable2
34 jsr 39 // Jumptomini-subroutineatoffset39(the
// secondfinallyclause)
37 aload_2 // Pushthereference(tothrownexception)
// fromlocalvariable2
38 athrow // Rethrowthesameexception
// Thesecondfinallyclause:
39 astore_3 // Storethereturnaddressinlocalvariable3
40 iload_0 // Pushtheintfromlocalvariable0(i)
41 iconst_3 // Pushconstant3
42 if_icmpeq 47 // Ifthetoptwointsonthestackareequal,jump
// tooffset47:if(i==3){
45 ret 3 // Jumptoreturnaddressstoredinlocalvariable3
47 goto 2 // Jumptooffset2(thetopofthewhile
// block):continue;
但是如果用Sun JDK 1.4之后的编译器编译,这段程序实际上被编译成截然不同的38个jvm指令(如下所示),其主要区别是finally 子句被inline到了正常代码和exception handling代码,避免了jsr/ret, 从而减少了JSR跳转/pop出栈/ret返回三条指令,在本例中,不但提高了运行效率,还减少了class文件的大小,其代价是finally block之中的内容被复制到了两个地方,如果finally block的指令很多,class文件可能会变大。
代码3:
static
int
hopAround();
Code:
0 :iconst_0 // pushconst0
1 :istore_0 // popintolocalvar0:i=0
// startbothtryblock
2 :iconst_1 // pushconst1
3 :istore_0 // popintoi
// startfirstfinallyblock(itisinlined)
4 :iconst_2 // pushconst2
5 :istore_0 // popintolocalvar0(i)
6 : goto 14 // skipexceptionhandling
// startexceptionhandlingforsecondtryblock
9 :astore_1 // popthrownexceptionreferenceintolocalvar1
// startfirstfinallyblock(inlineagain)
10 :iconst_2 // pushconst2
11 :istore_0 // popinto0
12 :aload_1 // pushthereference(theexception)fromlocalvar1
13 :athrow // throwexeption
// goonthefirsttryblock
14 :iconst_3 // pushconst3
15 :istore_0 // popintolocalvar0(i)
16 :iload_0 // pushvaluefromlocalvar0(i)
17 :istore_1 // popintolocalvar1(returnvalue)
// startsecondfinallyblock(inlined)
18 :iload_0 // pushvaluefromlocalvar0(i)
19 :iconst_3 // pushconst3
20 :if_icmpne 26 // ifthetoptwointsonthestackarenotequal,jumpto26
23 : goto 2 // loop
// goonthefirsttryblock
26 :iload_1 // pushvaluefromlocalvar1(returnvalue)
27 :ireturn // returnmethod
// startexceptionhandlingforfirsttryblock
28 :astore_2 // popthrownexceptionreferenceintolocalvar2
// startsecondfinallyblock(inlinedagain)
29 :iload_0 // pushvaluefromlocalvar0(i)
30 :iconst_3 // pushconst3
31 :if_icmpne 37 // ifthetoptwointsonthestackarenotequal,jumpto37
34 : goto 2 // loop
37 :aload_2 // pushthereference(theexception)fromlocalvar2
38 :athrow // throwexception
Exceptiontable:
fromtotargettype
2 4 9 any
9 10 9 any
2 18 28 any
28 29 28 any
Code:
0 :iconst_0 // pushconst0
1 :istore_0 // popintolocalvar0:i=0
// startbothtryblock
2 :iconst_1 // pushconst1
3 :istore_0 // popintoi
// startfirstfinallyblock(itisinlined)
4 :iconst_2 // pushconst2
5 :istore_0 // popintolocalvar0(i)
6 : goto 14 // skipexceptionhandling
// startexceptionhandlingforsecondtryblock
9 :astore_1 // popthrownexceptionreferenceintolocalvar1
// startfirstfinallyblock(inlineagain)
10 :iconst_2 // pushconst2
11 :istore_0 // popinto0
12 :aload_1 // pushthereference(theexception)fromlocalvar1
13 :athrow // throwexeption
// goonthefirsttryblock
14 :iconst_3 // pushconst3
15 :istore_0 // popintolocalvar0(i)
16 :iload_0 // pushvaluefromlocalvar0(i)
17 :istore_1 // popintolocalvar1(returnvalue)
// startsecondfinallyblock(inlined)
18 :iload_0 // pushvaluefromlocalvar0(i)
19 :iconst_3 // pushconst3
20 :if_icmpne 26 // ifthetoptwointsonthestackarenotequal,jumpto26
23 : goto 2 // loop
// goonthefirsttryblock
26 :iload_1 // pushvaluefromlocalvar1(returnvalue)
27 :ireturn // returnmethod
// startexceptionhandlingforfirsttryblock
28 :astore_2 // popthrownexceptionreferenceintolocalvar2
// startsecondfinallyblock(inlinedagain)
29 :iload_0 // pushvaluefromlocalvar0(i)
30 :iconst_3 // pushconst3
31 :if_icmpne 37 // ifthetoptwointsonthestackarenotequal,jumpto37
34 : goto 2 // loop
37 :aload_2 // pushthereference(theexception)fromlocalvar2
38 :athrow // throwexception
Exceptiontable:
fromtotargettype
2 4 9 any
9 10 9 any
2 18 28 any
28 29 28 any
Eclipse编译器显然也将finally inline了,Eclipse 3.2甚至disable了编译器不inline finally的选项,ECJ编译出来的hopAround()由39条指令组成:
代码4:
static
int
hopAround();
Code:
0 :iconst_0
1 :istore_0
// startbothtry
2 :iconst_1
3 :istore_0
// skipexceptionhandling
4 : goto 12
// exceptionhandlingforfirsttryblock
7 :astore_1
// firstfinallyblockinlined
8 :iconst_2
9 :istore_0
10 :aload_1
11 :athrow
// firstfinallyblockinlinedagain
12 :iconst_2
13 :istore_0
// goonsecondtryblock
14 :iconst_3
15 :istore_0
16 :iload_0
17 :istore_3
// secondfinallyblockinlined
18 :iload_0
19 :iconst_3
20 :if_icmpne 26
23 : goto 2
// return
26 :iload_3
27 :ireturn
// exceptionhandlingforsecondtry
28 :astore_2
// secondefinallyinlined
29 :iload_0
30 :iconst_3
31 :if_icmpne 37
34 : goto 2
37 :aload_2
38 :athrow
// whatdoesthismean?
39 : goto 2
Exceptiontable:
fromtotargettype
2 7 7 any
2 18 28 any
Code:
0 :iconst_0
1 :istore_0
// startbothtry
2 :iconst_1
3 :istore_0
// skipexceptionhandling
4 : goto 12
// exceptionhandlingforfirsttryblock
7 :astore_1
// firstfinallyblockinlined
8 :iconst_2
9 :istore_0
10 :aload_1
11 :athrow
// firstfinallyblockinlinedagain
12 :iconst_2
13 :istore_0
// goonsecondtryblock
14 :iconst_3
15 :istore_0
16 :iload_0
17 :istore_3
// secondfinallyblockinlined
18 :iload_0
19 :iconst_3
20 :if_icmpne 26
23 : goto 2
// return
26 :iload_3
27 :ireturn
// exceptionhandlingforsecondtry
28 :astore_2
// secondefinallyinlined
29 :iload_0
30 :iconst_3
31 :if_icmpne 37
34 : goto 2
37 :aload_2
38 :athrow
// whatdoesthismean?
39 : goto 2
Exceptiontable:
fromtotargettype
2 7 7 any
2 18 28 any
区别不大,Eclipse编译器将exception handling的代码放在了正常路径的finally block之前,多出来的一句是第39句goto 2, 比较匪夷所思, 因为这一句显然是无法被执行到的。还有一个区别是exception table缩减为两项,少掉的两条是针对exception handling代码自身的,Sun javac加上的这两条意欲何为也有点让人迷惑...也许因为这个原因,ECJ编译出来的class要稍微小一些(对于这个class, 222bytes vs. 235bytes)。
这两种主流Java编译器都对finally做了inline处理,所以我们在平常写java代码的时候应该注意在try/catch/finally里面不要做太多的分支,并且finally block中包含的内容尽量不要太多,否则class文件可能会多占用不少空间。
还有一个tips: javac和Eclipse编译器缺省都带有debug信息,加上-g:none选项之后,class文件要小一半,对于Eclipse编译器尤其明显。
最后一个tips送给有耐心读到这里的人: 请代码3中的14-17行,在进入finally block之前,JVM将把变量i当前的值存入另外一个本地变量(istore_1),并且在26-27行将此值返回,所以如果在finally block中修改了i的值,对返回值是没有影响的,如果要将修改过的i返回,请在finally中直接return i。更多finally实现的细节请参考Inside JVM:).