这个两个同学 @罗夏 @llzcmxiaotong 搬出了java生成的字节码,如果大家看得懂。很容易得出结论就是,对于Java来说,在循环外申明变量,效率不会变高。 @JonglyRan 更是说他实际测试过,效率没有差别。
但是有同学说道内存占用问题,认为“循环外申明变量内存占用会小很多”。我有话要说!!!
我想说的是: 循环外申明变量不但效率不会变高,在循环外申明变量,内存占用会更大!不但没有正面作用,反而有负面作用!
如果大家看字节码有困难,我们可以使用反编译工具。很容得出效率不会变高的结论
package test;
/*** Created by zhouhongyang@zbj.com on 8/8/2017.*/
public class VariableInsideOutsideLoopTest {
public void outsideLoop() {
Object o;
int i = 0;
while (++i < 100) {
o = new Object();
o.toString();
}
Object b = 1;
}
public void intsideLoop() {
int i = 0;
while (++i < 100) {
Object o = new Object();
o.toString();
}
Object b = 1;
}
}
上面的代码编译成class,反编译出来的样子是这样的:
Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//
package test;
public class VariableInsideOutsideLoopTest {
public VariableInsideOutsideLoopTest() {
}
public void outsideLoop() {
int i = 0;
while(true) {
++i;
if(i >= 100) {
Object b = Integer.valueOf(1);
return;
}
Object o = new Object();
o.toString();
}
}
public void intsideLoop() {
int i = 0;
while(true) {
++i;
if(i >= 100) {
Object b = Integer.valueOf(1);
return;
}
Object o = new Object();
o.toString();
}
}
}
纳里?反编译出来的代码一模一样!!! 结论不言而喻
那么他们的性能真正的一模一样吗? 性能除了cpu时间以外,还有个指标就是内存占用。
没办法,我也只能祭出神器javap了 (有了javap,java性能撕逼必胜,不会的大家请google学习一下)
public void outsideLoop();
Code:
0: iconst_0
1: istore_2
2: iinc 2, 1
5: iload_2
6: bipush 100
8: if_icmpge 27
11: new #2 // class java/lang/Object
14: dup
15: invokespecial #1 // Method java/lang/Object."":()V
18: astore_1
19: aload_1
20: invokevirtual #3 // Method java/lang/Object.toString:()Ljava/lang/String;
23: pop
24: goto 2
27: iconst_1
28: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
31: astore_3
32: return
LocalVariableTable:
Start Length Slot Name Signature
19 5 1 o Ljava/lang/Object;
0 33 0 this Ltest/VariableInsideOutsideLoopTest;
2 31 2 i I
32 1 3 b Ljava/lang/Object;
public void intsideLoop();
Code:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: bipush 100
8: if_icmpge 27
11: new #2 // class java/lang/Object
14: dup
15: invokespecial #1 // Method java/lang/Object."":()V
18: astore_2
19: aload_2
20: invokevirtual #3 // Method java/lang/Object.toString:()Ljava/lang/String;
23: pop
24: goto 2
27: iconst_1
28: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
31: astore_2
32: return
LocalVariableTable:
Start Length Slot Name Signature
19 5 2 o Ljava/lang/Object;
0 33 0 this Ltest/VariableInsideOutsideLoopTest;
2 31 1 i I
32 1 2 b Ljava/lang/Object;
嗯,字节码一模一样,真如前面两位大侠说的那样。
真的一模一样???
LocalVariableTable:
Start Length Slot Name Signature
19 5 1 o Ljava/lang/Object;
0 33 0 this Ltest/VariableInsideOutsideLoopTest;
2 31 2 i I
32 1 3 b Ljava/lang/Object; //
Start Length Slot Name Signature
19 5 2 o Ljava/lang/Object;
0 33 0 this Ltest/VariableInsideOutsideLoopTest;
2 31 1 i I
32 1 2 b Ljava/lang/Object; //
看到差别了吗? outsideLoop在stack frame中定义了4个slot, 而intsideLoop只定义了3个slot!!!
outsideLoop中,变量o和b分别占用了不同的slot,在intsideLoop中,变量o和b复用一个slot。
所以,outsideLoop的stack frame比intsideLoop多占用4个字节内存(一个slot占用4个字节,如果我没有记错)
真的就只有4个字节的差别?
由于在intsideLoop中,o和b复用了同一个slot,所以,当b使用slot 2的时候,这是变量o已经“不复存在”,所以o原来引用的对象就没有任何引用,它有可能立即被GC回收(注意是有可能,不是一定),腾出所占用heap内存。
所以,intsideLoop存在可能,在某些时间点,使用的heap内存比outsideLoop少。
当然这个例子中少的内存微不足道,但是假设这个方法执行时间很长,o引用的对象是一个大对象时,还是有那么点意义。
我发现这个问题有很多错误的看法且广为传播,近期准备写一个文章,长篇大论讨论这个问题。到时候请大家来捧场。