1. 程式人生 > >JAVA變數宣告在迴圈體內還是迴圈體外的爭論

JAVA變數宣告在迴圈體內還是迴圈體外的爭論

本文只是部分轉載  完整版本參考上面連結

Aray

程式猿

12 人贊同了該回答

這個兩個同學 

@羅夏

 搬出了java生成的位元組碼,如果大家看得懂。很容易得出結論就是,對於Java來說,在迴圈外申明變數,效率不會變高。 

 更是說他實際測試過,效率沒有差別。

但是有同學說道記憶體佔用問題,認為“迴圈外申明變數記憶體佔用會小很多”。我有話要說!!!

我想說的是: 迴圈外申明變數不但效率不會變高,在迴圈外申明變數,記憶體佔用會更大!不但沒有正面作用,反而有負面作用!

如果大家看位元組碼有困難,我們可以使用反編譯工具。很容得出效率不會變高的結論

package test;

/**
 * Created by 
[email protected]
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."<init>":()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."<init>":()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;    // <----- 看這裡,看加粗的3

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;     // <----- 看這裡,看加粗的2

看到差別了嗎? 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引用的物件是一個大物件時,還是有那麼點意義。

我發現這個問題有很多錯誤的看法且廣為傳播,近期準備寫一個文章,長篇大論討論這個問題。到時候請大家來捧場。