简体   繁体   English

将null赋给引用变量时的GC行为

[英]GC behavior when assigning null to reference variable

I was trying to understand the behavior of GC and I found something that interests me which I am unable to understand. 我试图理解GC的行为,我发现了一些让我感兴趣的东西,我无法理解。

Please see the code and output: 请参阅代码和输出:

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);          
    }
}

In above example if I assign holdLastObject to null then I get Total no of object garbage collected=9 . 在上面的例子中,如果我将holdLastObject指定为null,那么我得到Total no of object garbage collected=9 If I do not, I get 10 . 如果我不这样做,我会得到10

Can someone explain it? 有人可以解释一下吗? I am unable to find the correct reason. 我无法找到正确的理由。

Examining the bytecode helps reveal the answer. 检查字节码有助于揭示答案。

When you assign null to the local variable, as Jon Skeet mentioned, this is a definite assignment, and javac must create a local variable in the main method., as the bytecode proves: 当您为局部变量赋值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

In this case, the local variable will keep the last assigned value and will only be available for garbage collection when it goes out of scope. 在这种情况下,局部变量将保留最后一个赋值,并且只有在超出范围时才可用于垃圾收集。 Since it's defined in main it only goes out of scope when the program is terminated, at the time you print i , it isn't collected. 由于它在main定义,它只在程序终止时超出范围,在您打印i ,它不会被收集。

If you do not assign a value to it, since it's never used outside the loop, javac optimizes it to a local variable in the for loop's scope, which can of course be collected before the program terminates. 如果你没有给它赋值,因为它从不在循环之外使用,javac将它优化for循环范围内的局部变量,当然可以在程序终止之前收集它。

Examining the bytecode for this scenario shows that the entire block for LINENUMBER 12 is missing, hence proving this theory right. 检查此场景的字节码表明缺少LINENUMBER 12的整个块,因此证明了这一理论是正确的。

Note: 注意:
As far as I know, this behavior is not defined by the Java standard, and may vary between javac implementations. 据我所知,这种行为不是由Java标准定义的,并且可能因javac实现而异。 I've observed it with the following version: 我用以下版本观察过它:

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)

I suspect it's due to definite assignment. 我怀疑这是由于明确的任务。

If you assign a value to holdLastObject before the loop, it's definitely assigned for the whole method (from the point of declaration onwards) - so even though you don't access it after the loop, the GC understands that you could have written code that accessses it, so it doesn't finalize the last instance. 如果在循环之前为holdLastObject赋值,那么它肯定是为整个方法分配的(从声明之后开始) - 所以即使你在循环之后没有访问它,GC也知道你可以编写代码访问它,因此它不会完成最后一个实例。

As you don't assign a value to the variable before the loop, it's not definitely assigned except within the loop - so I suspect the GC treats it as if it were declared in the loop - it knows that no code after the loop could read from the variable (because it's not definitely assigned) and so it knows it can finalize and collect the last instance. 因为你没有在循环之前为变量赋值,所以除了在循环中之外没有明确赋值 - 所以我怀疑GC将它视为在循环中声明它 - 它知道循环之后没有代码可以读取从变量(因为它没有明确分配),所以它知道它可以完成并收集最后一个实例。

Just to clarify what I mean by this, if you add: 只是为了澄清我的意思,如果你添加:

System.out.println(holdLastObject);

just before the System.gc() line, you'll find it won't compile in your first case (without the assignment). 就在System.gc()行之前,你会发现它不会在你的第一种情况下编译(没有赋值)。

I suspect this is a VM detail though - I'd hope that if the GC could prove that no code was actually going to read from the local variable, it would be legal for it to collect the final instance anyway (even if it isn't implemented that way at the moment). 我怀疑这是一个虚拟机细节 - 我希望如果 GC可以证明没有代码实际上是从本地变量中读取的,那么无论如何收集最终实例都是合法的(即使它不是' t目前以这种方式实施)。

EDIT: Contrary to TheLostMind's answer, I believe the compiler gives this information to the JVM. 编辑:与TheLostMind的答案相反,我相信编译器会将此信息提供给JVM。 Using javap -verbose GCTest I found this without the assignment: 使用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 */

and this with the assigment: 分配:

  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 */

Note the difference in the locals part of the first entry. 请注意第一个条目的locals部分的差异。 It's odd that the class GCTest entry doesn't appear anywhere without the initial assignment... 奇怪的是,如果没有初始分配, class GCTest条目就不会出现在任何地方 ......

I didn't find any major differences in the byte code for both cases (so not worth posting the byte code here). 我没有发现两种情况的字节代码有任何重大差异(所以不值得在这里发布字节代码)。 So my assumption is that this is due to JIT / JVM optimizations. 所以我的假设是这是由于JIT / JVM优化。

Explanation : 说明:

case -1 : 情况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
}

Here, note that you have not initialized holdLastObject to null . 请注意 ,您尚未将holdLastObject初始化为null So, outside the loop, it cannot be accessed (you will get a compile time error). 因此,在循环之外,它无法访问(您将收到编译时错误)。 This means that *the JVM figures out that the field is not being used in the later part. 这意味着* JVM发现该字段未在后面部分中使用。 Eclipse gives you that message. Eclipse为您提供该消息。 So, the JVM will create and desroy everything inside the loop itself . 因此,JVM将创建并消除循环内部的所有内容。 So, 10 objects Gone. 所以,10个物体已经过时了。

Case -2 : 案例-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
    }

In this case, since the field is initialized to null, it is created outside the loop and hence a null reference is pushed into its slot in the local variables table . 在这种情况下,由于字段被初始化为null,因此它在循环外部创建 ,因此null reference被推入其局部变量表中的槽中。 Thus the JVM understands that the field is accessible from outside so it does not destroy the last instance it keeps it alive as it is still accessible /readable .So unless you explictly set the value of the last reference to null, it exists and is reachable. 因此,JVM理解该字段可以从外部访问 ,因此它不会破坏它保持活动的最后一个实例 ,因为它仍然是可访问/可读的 。因此,除非您明确地将最后一个引用的值设置为null,否则它存在并且可以访问。 Hence 9 instances will be ready for GC. 因此,9个实例将为GC做好准备。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java - 为什么为变量赋值“null”并不能使它可用于GC? (在此代码段中) - Java - why does assigning “null” to variable does not make it available for GC? (In this code snippet) 将成员作为参数传递给新线程时的局部变量引用和GC - Local variable reference and GC when passing member to new Thread as argument 变量在赋值时仍然为空 - Variable is still null when assigning it a value 在Java中分配空引用时是否可能抛出runtimeexception? - Is it possible for for a runtimeexception to be thrown when assigning a null reference in Java? 如果字符串为空,则分配给变量 - Assigning to a variable if string is null 什么是未初始化的引用变量,它不为null - What is a uninitialized reference variable when it's not null 如果没有引用变量,java GC如何工作? - How does java GC work if there is no reference variable? 静态变量设置为null是否可用于GC? - Static variable set to null available for GC? 为什么此扫描程序将null分配给变量? - Why is this Scanner assigning null to a variable? 在paintComponent方法中将Graphics引用分配给Graphics2D引用变量时,为什么没有运行时错误? - why is there not a Runtime error when assigning Graphics reference to Graphics2D reference variable in paintComponent method?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM