垃圾收集器行为 - "marking the objects" 是如何工作的?

Garbage Collector behaviour - How does "marking the objects" work?

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

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

当程序到达 // 此处时,有多少对象将被标记为准备好为 GC 销毁?

正确答案是 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 的这种行为?

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

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

If the value p being boxed is true, false, a byte, or a char in the range \u0000 to \u007f, or an int or short number between -128 and 127 (inclusive), then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.

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

不用说,您不应该依赖它来进行相等性检查,因为 JVM 缓存的值的确切范围由每个特定的实现决定:

Ideally, boxing a given primitive value p, would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rules above are a pragmatic compromise. The final clause above requires that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, this formulation disallows any assumptions about the identity of the boxed values on the programmer's part. This would allow (but not require) sharing of some or all of these references.

This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

第一个例子:

The correct answer is 2 ...

实际上,我认为取决于各种因素,0 到 4 之间的任何数字 都可能 是正确的。

  • 首先,不清楚一开始创建了多少个对象。显然创建了两个 Test 个对象。但是创建的 Short 对象的数量可以是 0、1 或 2。规范说 Short 可以 保留值的缓存,但是对于 200 , 不需要。如果它不缓存它们,可能会创建 2 Short(2) 个对象。如果是,则将创建 0 或 1。 (如果其他代码已经缓存了 Short(200),将创建 None)。

  • 接下来的问题是在指定的点 "live" 真正的变量是什么。显然,a1a2 仍在范围内。但是,GC 将被允许将 a1a2 都视为 "dead",因为它们此时无法影响可观察行为方法。因此我们不能说第二个 Test 实例是否会被 GC 视为可达。

  • 最后,由于将 null 赋值给 a1 不会影响方法的可观察行为,因此优化器优化该赋值是(可以说)合法的离开。因此,您可能会遇到 a1 仍然包含对 GC 可见的 Test 实例的引用的情况。

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