簡體   English   中英

將null賦給引用變量時的GC行為

[英]GC behavior when assigning null to reference variable

我試圖理解GC的行為,我發現了一些讓我感興趣的東西,我無法理解。

請參閱代碼和輸出:

public class GCTest {
    private static int i=0;

    @Override
    protected void finalize() throws Throwable {
        i++; //counting garbage collected objects
    }

    public static void main(String[] args) {        
        GCTest holdLastObject; //If I assign null here then no of eligible objects are 9 otherwise 10.

        for (int i = 0; i < 10; i++) {            
             holdLastObject=new GCTest();             
        }

        System.gc(); //requesting GC

        //sleeping for a while to run after GC.
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // final output 
        System.out.println("`Total no of object garbage collected=`"+i);          
    }
}

在上面的例子中,如果我將holdLastObject指定為null,那么我得到Total no of object garbage collected=9 如果我不這樣做,我會得到10

有人可以解釋一下嗎? 我無法找到正確的理由。

檢查字節碼有助於揭示答案。

當您為局部變量賦值null ,正如Jon Skeet所提到的,這是一個明確的賦值,並且javac 必須main方法中創建一個局部變量。,因為字節碼證明:

// access flags 0x9
public static main([Ljava/lang/String;)V
  TRYCATCHBLOCK L0 L1 L2 java/lang/InterruptedException
 L3
  LINENUMBER 12 L3
  ACONST_NULL
  ASTORE 1

在這種情況下,局部變量將保留最后一個賦值,並且只有在超出范圍時才可用於垃圾收集。 由於它在main定義,它只在程序終止時超出范圍,在您打印i ,它不會被收集。

如果你沒有給它賦值,因為它從不在循環之外使用,javac將它優化for循環范圍內的局部變量,當然可以在程序終止之前收集它。

檢查此場景的字節碼表明缺少LINENUMBER 12的整個塊,因此證明了這一理論是正確的。

注意:
據我所知,這種行為不是由Java標准定義的,並且可能因javac實現而異。 我用以下版本觀察過它:

mureinik@computer ~/src/untracked $ javac -version
javac 1.8.0_31
mureinik@computer ~/src/untracked $ java -version
openjdk version "1.8.0_31"
OpenJDK Runtime Environment (build 1.8.0_31-b13)
OpenJDK 64-Bit Server VM (build 25.31-b07, mixed mode)

我懷疑這是由於明確的任務。

如果在循環之前為holdLastObject賦值,那么它肯定是為整個方法分配的(從聲明之后開始) - 所以即使你在循環之后沒有訪問它,GC也知道你可以編寫代碼訪問它,因此它不會完成最后一個實例。

因為你沒有在循環之前為變量賦值,所以除了在循環中之外沒有明確賦值 - 所以我懷疑GC將它視為在循環中聲明它 - 它知道循環之后沒有代碼可以讀取從變量(因為它沒有明確分配),所以它知道它可以完成並收集最后一個實例。

只是為了澄清我的意思,如果你添加:

System.out.println(holdLastObject);

就在System.gc()行之前,你會發現它不會在你的第一種情況下編譯(沒有賦值)。

我懷疑這是一個虛擬機細節 - 我希望如果 GC可以證明沒有代碼實際上是從本地變量中讀取的,那么無論如何收集最終實例都是合法的(即使它不是' t目前以這種方式實施)。

編輯:與TheLostMind的答案相反,我相信編譯器會將此信息提供給JVM。 使用javap -verbose GCTest我發現這沒有賦值:

  StackMapTable: number_of_entries = 4
    frame_type = 253 /* append */
      offset_delta = 2
      locals = [ top, int ]
    frame_type = 249 /* chop */
      offset_delta = 19
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/InterruptedException ]
    frame_type = 4 /* same */

分配:

  StackMapTable: number_of_entries = 4
    frame_type = 253 /* append */
      offset_delta = 4
      locals = [ class GCTest, int ]
    frame_type = 250 /* chop */
      offset_delta = 19
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/InterruptedException ]
    frame_type = 4 /* same */

請注意第一個條目的locals部分的差異。 奇怪的是,如果沒有初始分配, class GCTest條目就不會出現在任何地方 ......

我沒有發現兩種情況的字節代碼有任何重大差異(所以不值得在這里發布字節代碼)。 所以我的假設是這是由於JIT / JVM優化。

說明:

情況1 :

public static void main(String[] args) {
  GCTest holdLastObject; //If I assign null here then no of eligible objects are 9 otherwise 10.
     for (int i = 0; i < 10; i++) {
         holdLastObject=new GCTest();
    }
    //System.out.println(holdLastObject); You can't do this here. holdLastObject might not have been initialized.
     System.gc(); //requesting GC
}

請注意 ,您尚未將holdLastObject初始化為null 因此,在循環之外,它無法訪問(您將收到編譯時錯誤)。 這意味着* JVM發現該字段未在后面部分中使用。 Eclipse為您提供該消息。 因此,JVM將創建並消除循環內部的所有內容。 所以,10個物體已經過時了。

案例-2:

 public static void main(String[] args) {
      GCTest holdLastObject=null; //If I assign null here then no of eligible objects are 9 otherwise 10.
         for (int i = 0; i < 10; i++) {
             holdLastObject=new GCTest();
        }
        //System.out.println(holdLastObject); You can't do this here. holdLastObject might not have been initialized.
         System.gc(); //requesting GC
    }

在這種情況下,由於字段被初始化為null,因此它在循環外部創建 ,因此null reference被推入其局部變量表中的槽中。 因此,JVM理解該字段可以從外部訪問 ,因此它不會破壞它保持活動的最后一個實例 ,因為它仍然是可訪問/可讀的 。因此,除非您明確地將最后一個引用的值設置為null,否則它存在並且可以訪問。 因此,9個實例將為GC做好准備。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM