局部变量位置

package org.agoncal.sample.jmh;

public class Test {

    public void t1() {
        Object a;
        for (int i = 0; i < 100; i++) {
            a = new Object();
            System.out.println(a.getClass());
        }
    }

    public void t2() {
        for (int i = 0; i < 100; i++) {
            Object a = new Object();
            System.out.println(a.getClass());
        }
    }
}

看资料网上大多说倾向 t1 更好,因为它重用了变量,而不是必每次都分配。

字节码上的区别

t1

 0 iconst_0 将常量 int 型的 0 压入栈
 1 istore_2 从栈顶弹出一个 int 的数据,并保存到 LocalVariableTable 的 slot 为 2的局部变量中(在这里,即为 int i = 0)
 2 iload_2  从 slot 为 2 的局部变量中,将它的值压入栈(即将 i 的值又压入栈)
 3 bipush 100 将字节型 byte 的 100 数值压入栈
 5 if_icmpge 32 弹出栈顶两个 int 并比较,如果 >= ,则跳到行 32(即 return),这里的意思就是 if (i>=100) {goto 第32行}
 8 new #2 <java/lang/Object>  创建一个对象 Object,这时一个 object 的引用压入栈
11 dup 弹出栈顶一个字,并压入2次,(这时,即是有2个 object 引用了)
12 invokespecial #1 <java/lang/Object.<init>> 弹出一个 object引用,并调用它的 init 方法,即初始化对象
15 astore_1 从栈顶弹出一个对象引用,并保存到 slot 为 1 局部变量中(这里即为 a = new Object())
16 getstatic #3 <java/lang/System.out> 获取静态域 System.out 对象,并压入栈
19 aload_1   将局部变量 slot 为1 的值又压入栈(a)
20 invokevirtual #4 <java/lang/Object.getClass>  弹出对象a,并调用 a.getClass() 方法,最的将结果压入栈
23 invokevirtual #5 <java/io/PrintStream.println> 弹出对象 System.out ,并调用 PrintStream.println() 方法(因为该方法并没返回值,所以不用压栈)
26 iinc 2 by 1 将局部变量表中 slot 为 2 局部变量,增加 1
29 goto 2 跳转到 pc 为 2的地方
32 return 返回
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           16      16     1     a   Ljava/lang/Object;
            2      30     2     i   I
            0      33     0  this   Lorg/agoncal/sample/jmh/Test;      

t2

 0 iconst_0 将常量 int 型的 0 压入栈
 1 istore_1 从栈顶弹出一个 int 的数据,并保存到 LocalVariableTable 的 slot 为 1的局部变量中(在这里,即为 int i = 0)
 2 iload_1 从 slot 为 1 的局部变量中,将它的值压入栈(即将 i 的值又压入栈)
 3 bipush 100 将字节型 byte 的 100 数值压入栈
 5 if_icmpge 32 弹出栈顶两个 int 并比较,如果 >= ,则跳到行 32(即 return),这里的意思就是 if (i>=100) {goto 第32行}
 8 new #2 <java/lang/Object> 创建一个对象 Object,这时一个 object 的引用压入栈
11 dup 弹出栈顶一个字,并压入2次,(这时,即是有2个 object 引用了)
12 invokespecial #1 <java/lang/Object.<init>> 弹出一个 object引用,并调用它的 init 方法,即初始化对象
15 astore_2 从栈顶弹出一个对象引用,并保存到 slot 为 2 局部变量中(这里即为 a = new Object())
16 getstatic #3 <java/lang/System.out> 获取静态域 System.out 对象,并压入栈
19 aload_2  将局部变量 slot 为2 的值又压入栈(a) 
20 invokevirtual #4 <java/lang/Object.getClass> 弹出对象a,并调用 a.getClass() 方法,最的将结果压入栈
23 invokevirtual #5 <java/io/PrintStream.println> 弹出对象 System.out ,并调用 PrintStream.println() 方法(因为该方法并没返回值,所以不用压栈)
26 iinc 1 by 1 将局部变量表中 slot 为 2 局部变量,增加 1 
29 goto 2 跳转到 pc 为 2 的地方
32 return 返回

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           16      10     2     a   Ljava/lang/Object;
            2      30     1     i   I
            0      33     0  this   Lorg/agoncal/sample/jmh/Test;

LocalVariableTable 中的 start, length, slot 说明

start: 表示以相对于该方法的起始位置的字节数偏移值 length: 表示该局部变量的可见性长度(即从相对于 start 的位置开始可见,到 length 个字节数为止) slot:局部变量的存储单元位置(1个 slot 可以存储除 double, long 之外的任何类型的数据,比如 int ,byte , short,对象引用等)

结论

通过对比它们的字节码,可以看到它们的唯一区别,就是 Object a 在 LocalVariableTable 中 slot 的位置不同而已,其他的就没有任何区别了。

所以,这两者的代码效率是一样的。(包括内存分配的效率)

个人倾向于将将变量的作用域范围,缩小到最小的风格。即:

for (int i=0;i<100;i++){
    Object a = new Object()
}