java编译器对finally的优化

<Inside JVM>是市面上少数几本系统介绍JVM的书籍,(作者Bill Venners的网站 artima也是非常有趣的Java技术社区),这本书最有意思的地方可能就是作者为了演示JVM内部工作原理编写的 Applet, 非常值得向大家推荐。<Inside JVM>最新的是第二版,也有些年头了,尽管JVM规范本身的变化并不大,但是java编译器的有些细节处理还是跟书里面不一样了,比如说现在的java编译器一般都会对finally作inline处理。

这个 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 ;
}
}
}
}
}


其主要目的演示的是finally子句的处理, 在Inside JVM和JVM Spec的7.13节当中,finally都是通过jsr和ret指令实现的,按照书中的说法,hopAround()方法将被编译成47个jvm指令,如下所示:

代码2:
/**/ /*
*copiedfrom
http://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



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


区别不大,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:).
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值