Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/svg/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 将null赋值给引用变量时的GC行为_Java_Garbage Collection - Fatal编程技术网

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

Java 将null赋值给引用变量时的GC行为,java,garbage-collection,Java,Garbage Collection,我试图理解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[]

我试图理解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);          
    }
}
公共类GCTest{
私有静态int i=0;
@凌驾
受保护的void finalize()抛出可丢弃的{
i++;//计算垃圾收集的对象
}
公共静态void main(字符串[]args){
GCTest holdLastObject;//如果我在这里赋值null,则符合条件的对象的数量为9,否则为10。
对于(int i=0;i<10;i++){
holdLastObject=new GCTest();
}
System.gc();//请求gc
//睡眠一段时间以在GC之后运行。
试一试{
睡眠(200);
}捕捉(中断异常e){
e、 printStackTrace();
}
//最终产量
System.out.println(“`Total no of object garbage collected=`”+i);
}
}
在上面的示例中,如果我将
holdLastObject
赋值为null,那么我将得到
对象垃圾回收总数=9
。如果我没有,我会得到
10


有人能解释一下吗?我找不到正确的原因。

我怀疑这是由于明确的任务

如果在循环之前为
holdLastObject
赋值,那么它肯定是为整个方法赋值的(从声明开始)-因此,即使在循环之后您没有访问它,GC也知道您可以编写访问它的代码,因此它不会最终确定最后一个实例

由于在循环之前没有为变量赋值,因此除了在循环内没有明确赋值-因此我怀疑GC将其视为在循环中声明的变量-它知道循环后没有代码可以读取变量(因为没有明确赋值)因此它知道它可以完成并收集最后一个实例

为了澄清我的意思,如果你补充:

System.out.println(holdLastObject);
就在
System.gc()
行之前,您会发现它在第一种情况下无法编译(没有赋值)

不过,我怀疑这是一个VM细节——我希望如果GC能够证明没有代码真正从局部变量读取,那么它收集最终实例也是合法的(即使它目前没有以这种方式实现)

编辑:与LostMind的回答相反,我相信编译器会将这些信息提供给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
部分的差异。奇怪的是,
类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
}

在这种情况下,由于字段被初始化为null,因此它是在循环外部创建的,因此,
null引用
被推送到局部变量表中的插槽中。因此,JVM知道该字段可以从外部访问,因此它不会破坏最后一个实例,因为它仍然是可访问/可读的。因此,除非您明确地将最后一个引用的值设置为null,否则它存在并且是可访问的。因此,9个实例将为GC做好准备。

检查字节码有助于揭示答案

当您将
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)

诚然,我不知道编译器是否在字节码中指明了一个变量的确切赋值位置,或者VM是否能自己解决这个问题实际上,如果您将其初始化为
null,它会将
null引用
推入局部变量表中的变量。
:Pwith null-->
0:aconst\u null 1:astore\u 1
。。如果没有空-->
0:iconst_0 1:istore_2
@TheLostMind:是的,但是在循环中执行与在循环外执行有什么区别,例如,在字节码方面?@TheLostMind:只是在许多情况下,这种区别并不相关。我把它看作是一个很大的黑匣子。(同样地,JIT的边界到底在哪里?)我不介意——而其他信息,比如JVM是否在推断这些信息,或者它是否在使用字节码中的内容,则是一个更具体的区别。无论如何,我认为我们应该在这一点上停止聊天…@Amitd:在运行发布模式构建时,而不是在调试器下,.NET JIT可能非常具有攻击性-它注意到不再访问局部变量,并将其作为GC根忽略。见鬼,它还可以收集对象,而实例
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)