[快乐开发] 关于在finally子句中赋值的问题分析

<>++< 2011-04-22

     先引用一篇文章:使用javap分析finally块中return值

里面写了testInt() testRef() 以及testArr()三个方法 它们均在return之后的finally子句中对该对象进行了 直接 间接 的重复赋值,但最后return得到的结果却不同。

目前讨论的结论是 return 使用一个单独的寄存器,return之后这个寄存器里的值不被修改了。

不过我觉得原因可能不是这样,因为jvm中的寄存器数量极其有限,不可能做到一个return使用一个单独的寄存器

--------------背景介绍完毕,下面是调查结果--------------------

先收集的三则资料:
1. 每个jvm虚拟机线程有它自己的寄存器,每个jvm线程执行一个单方法的代码。如果这个方法不是本地方法,那么寄存器包含了当前正在被执行的jvm指令的地址。这个可以参见《inside jvm》第3.5.1节pc寄存器
2. jvm会为每一个方法分配一个java frame,它包含了一个操作数栈。而绝大多数java虚拟机指令从当前java frame的操作数栈取值,对它们进行操作。这个可以参见《inside jvm》3.6.2节操作数栈
3. 而Java虚拟机的寄存器有四种:
pc:Java程序计数器。
optop:指向操作数栈顶端的指针。
frame:指向当前执行方法的执行环境的指针。
vars:指向当前执行方法的局部变量区第一个变量的指针。


从上边的三点可以总结为:
一个jvm的线程对应一个寄存器,一个单方法代码;jvm同时为一个方法分配一个包含操作数栈的java frame
而寄存器和该方法的操作数栈的关系是:寄存器保存操作数栈顶的指针。
而回到咱们讨论的话题,对于方法的调用。用的是optop这种寄存器,optop寄存器保存了该方法中操作数栈顶的指针。所以并不是return语句单独使用一个寄存器。return之后这个寄存器里的值不被修改了。

那么为什么testInt(),testArr()和testRef()返回的值不一样呢?其实是finally的原因
参见4.9.6节异常和finally中所写:
【为了实现try_finally构造,java编译器与两条特殊的指令jsr(跳转到子程序)和ret(从子程序返回)
一起使用异常处理设施,而finally子句被编译成该方法的子程序,当执行jsr时,会把返回地址
(也就是jsr后边那条指令的地址)作为returnAddress的一个值压入操作数栈,
而ret指令从局部变量中取回返回地址】
那么再看最上边引用的那篇文章, testInt()和testArr()方法的反编译代码的最后都有ret指令(取了返回地址),
而testRef()则没有ref指令(没有取回返回地址)
这就是问题的所在!

大概大家更多的疑问是在testRef()和testArr()的返回值上,两者均是引用类型,为什么返回值不同。

我觉得原因可能是这样:(目前只是猜测)

在testArr()方法中的 return x[0];这行 是调用了String类型的toString()方法,将返回值return,这个是基础知识,无需解释;而testRef()里的 return ref;这行也调用了Ref类中重写的toString()方法,那么两个toString()的不同点在于String的toString()的实现是 return this; 而Ref类中的toString()的实现是 return String.valueOf ( x );也就是说 testRef()和TestArr()返回的对象其实是不同的 一个是String类型x[0]对象本身 一个是Ref类中的全局变量x,所以在进入Ref类的toString()方法时 操作数栈就不同了 所以归纳为 testInt()和testArr()方法对应的寄存器保存的是当前操作数栈顶的值1,而testRef()对应的寄存器保存的是Ref类中toString()方法中操作栈顶的值2而不是当前操作栈顶的值1。进而testRef()的反编译代码中就没有出现ret指令。


 

结论是 只要赋值和return的是同一个对象 return语句执行后 该值就再也不变了 至于TestRef这个方法 赋值和返回的分别是ref.x和ref所以值被改变了

可参见:

 

Global site tag (gtag.js) - Google Analytics