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