垃圾收集器行为 - "如何标记对象"工作?

时间:2015-02-12 13:51:17

标签: java garbage-collection

我发现了一个关于垃圾收集器的有趣问题 对于以下代码:

class Test {
      Short x = 200;
}

public class MyTest {
    public static void main(String[] args) {
         Test a1 = new Test();
         Test a2 = new Test();
         a1 = null;
         // here
    }
}

当程序到达//这里时,有多少个对象将被标记为准备销毁?

正确的答案是2但是对于类似的代码:

class Test {
      Short x = 5;
}

public class MyTest {
    public static void main(String[] args) {
         Test a1 = new Test();
         Test a2 = new Test();
         a1 = null;
         // here
    }
}

正确的答案是1。

我想到了JVM小值缓存,但我不确定 任何人都可以解释GC的这种行为吗?

2 个答案:

答案 0 :(得分:0)

GC通常会将所有全局变量和所有线程上的变量用作根,将它们标记为活动(可达),然后递归地跟随它们包含的引用,并将这些引用的对象标记为活动,依此类推。未被标记为活着的对象将被假定为死亡(无法访问)并将被收集。

对于盒装类型,它们是类,因此它们的实例受GC周期的影响,a JVM implementation is required to cache at least a particular range的数值大约为零:

  

如果装箱的值p 为true,false,字节或字符在\ u0000到\ u007f范围内,或在-128到127之间的int或短号(包括),然后让r1和r2成为p的任意两次拳击转换的结果。 始终是r1 == r2。

的情况

因此,这些缓存的实例,即使它们无法从用户代码中的GC根目录到达,也始终被JVM本身标记为活动,因此不会被收集。

毋庸置疑,您不应该依赖此进行相等性检查,因为JVM缓存的确切值范围由每个特定实现决定:

  

理想情况下,装箱给定的原始值p将始终产生相同的参考。实际上,使用现有的实现技术可能不可行。上述规则是一种务实的妥协。上面的最后一个条款要求将某些常见值装入无法区分的对象中。实现可以懒惰地或急切地缓存这些。 对于其他值,此公式不允许对程序员的盒装值的身份进行任何假设。这将允许(但不要求)共享部分或全部这些引用。

     

这确保了在大多数情况下,行为将是所需的行为,而不会造成过度的性能损失,尤其是在小型设备上。 内存限制较少的实现可能会缓存所有char和short值,以及-32K到+ 32K范围内的int和long值。

答案 1 :(得分:0)

对于第一个例子:

  

正确答案是2 ...

实际上,我认为根据各种因素,0到4之间的任何数字都可以正确。

  • 首先,目前尚不清楚首先创建了多少个对象。显然创建了两个Test个对象。但是,创建的Short个对象的数量可以是0,1或2.规范说Short 可以保留值的缓存,但对于200,它是不必需。如果它不缓存它们,则可以创建2个Short(2)个对象。如果是,则将创建0或1。 (如果某些其他代码已经缓存了Short(200)),则无法创建。

  • 接下来的问题是什么变量真的是"生活"在指定的点。显然,a1a2仍在范围内。但是,GC可以同时将a1a2视为"死亡"因为他们不能影响那时的可观察行为方法。因此,我们无法确定第二个Test实例是否会被GC视为可达。

  • 最后,由于nulla1的分配不会影响方法的可观察行为,因此优化器优化该分配(合理地)是合法的。因此,您可能会遇到a1仍然包含对GC可见的Test实例的引用的情况。

在第二种情况下,JLS保证Short(5)将被缓存(通过自动装箱),因此我们可以确保代码最多会创建 1 Short(5)实例。然而,其他不确定因素仍然适用。