【问题标题】:the work of the garbage collector in JavaJava中垃圾收集器的工作
【发布时间】:2015-08-30 15:45:17
【问题描述】:

在调用System.gc() 时哪些对象可用于垃圾回收,为什么?

public class GCTest {
    static class A {
        private String myName;
        public A(String myName) {
            this.myName = myName;
        }
    }

    public static void main(String[] args) {
        A a1 = new A("a1");
        A a2 = new A("a2");
        ArrayList list = new ArrayList();
        list.add(a1);
        A[] mas = new A[2];
        mas[0] = a2;
        a2 = a1;
        clear(mas);
        a1 = null;
        a2 = null;
        System.gc();
        // some code
        ...
    }

    private static void clear(A[] mas) {
        mas = null;
    }
} 

如果object == null 会变成垃圾吗?

我认为 a1a2mas 在调用 System.gc() 时可用于垃圾收集,因为它声明为 null。还是我错了?

【问题讨论】:

标签: java garbage-collection


【解决方案1】:

首先,您的程序变量永远不会“可用于垃圾收集”。 GC 只收集对象,程序变量是对象的引用,而不是对象本身。 (引用 Java 变量的常用术语混淆了这一点。)

其次,a1a2mas 引用的所有对象都不会被 GC 处理。尽管a1a2 已设置为null,但还是会这样做。这是因为对这些对象的引用仍然可以通过listmas 访问。为什么a2 引用的对象即使在调用clear() 之后仍然可以通过mas 访问?因为您无法通过将变量传递给方法clear() 来更改main() 中变量mas 的值。您在该方法中所做的只是更改形式参数(也称为mas),它是与main() 中的局部变量分开的变量。 Java 严格按值传递。然而,当涉及到对象时,总是被传递的是一个reference

一般来说,规则是,只有当对象无法从任何程序变量直接或间接访问时,才会对对象进行 GC。

【讨论】:

  • 我承认这是工作中的测试任务之一。很可能我不会派他去,但我对它的工作原理非常感兴趣。谢谢你解释。如果您推荐一篇有趣的文章或书籍,我还想了解 Java 的复杂性和工作原理
  • @ip696 - 我总是建议从the Java tutorials 开始。它们准确且非常彻底。
【解决方案2】:

此时 GC 没有可用的东西:

  • 您仍然有一个引用 (list) 引用 ArrayList,其中包含最初由 a1 引用的对象(内部带有“a1”字符串的对象)
  • 您仍然有一个引用 (mas) 引用包含最初由 a2 引用的对象的数组(内部带有“a2”字符串的那个)

注意你的方法

private static void clear(A[] mas) {
    mas = null;
}

什么都不做。

Java 是按值传递的。因此,当调用此方法时,会创建对数组的引用的副本并将其传递给该方法。然后该方法将 null 分配给原始引用的副本,而原始引用保持不变。

更多解释请见Is Java "pass-by-reference" or "pass-by-value"?

【讨论】:

    【解决方案3】:

    没有人有资格获得 GC:

    1. ArrayList 仍然可以访问并引用a1
    2. a2a1 都指向同一个对象。
    3. 最重要的是,由于 Java 是按值传递的,因此方法 clear不会实际上清除从 main 传递的数组 mas

    下面是GC调用时内存中的对象示意图:

    【讨论】:

      【解决方案4】:

      关于对象可达性的唯一指定规则如下(§12.6.1):

      可访问对象是可以在任何活动线程的任何潜在持续计算中访问的任何对象。

      检查您的代码很明显,在任何潜在的持续计算中都可以访问 no 对象。在main 完成后,将不存在对它们中的任何一个的引用;此外,System.gc() 是程序中的最后一条语句,并且该语句的计算不会访问那些或任何其他对象(它在 Java 级别根本没有语义)。因此,无论垃圾收集的实际实现是否实现了这一点,所有对象实际上无法访问

      【讨论】:

      • 这并不是真正的最后一条语句,因为该方法以// some code ..." 结尾,这很可能意味着在调用 gc() 之后有更多代码。即使没有代码跟随,GC 是否有足够的信息知道没有代码跟随并且变量将永远不会被再次使用。我不是低级 GC 和 JVM 专家,但我不认为是这样。如果您遵守规范,我认为您的答案实际上是正确的,但有点……吹毛求疵,因为在实践中,AFAIK 那时没有任何东西有效地符合 GC 的条件。
      • 我认为这是一个挑剔的问题:它来自作业/面试,它显然要求直接从规范中得出权威答案。因此,这在很大程度上取决于“某些代码”是什么。顺便说一句,当人们使用松散的推理来决定 OOME 在某个时候是否可能时,它变得不那么挑剔了。例如,即使是超出范围的变量也可以在实践中阻止有效地无法访问的对象被 GC(这是我当前答案的另一面)。
      • 好的。 +1 提醒我们规格。但是考虑到 OP 还不了解引用是如何传递给方法的,因此他可能正在面试入门级职位,如果面试官期望得到如此精确的答案,我会感到非常惊讶:-)
      • 不错,题中间接探究了对其他语言语义的理解;可以通过询问某个语句是否会引发 NPE 来更直接地调查这些问题。
      • 这里:void x() { { long[] longs1 = new long[Integer.MAX_VALUE]; } System.gc(); long[] longs2 = new long[Integer.MAX_VALUE]; } 注意嵌套的 {} 块。 longs1 在分配longs2 时显然无法访问,但这仍然会产生OOME。前段时间有机会详细介绍一下:stackoverflow.com/questions/13531004/…
      【解决方案5】:

      调用System.gc()时new A("a1")、new A("a2")和mas创建的对象没有被垃圾回收。

      线程直接或间接引用的任何对象都不能用于 gc。线程正在运行此方法,因此将保留局部变量。

      new A("a1")(姑且称之为A1)创建的对象为例,

      在 System.gc() 运行的那一刻,list 在其内部结构中引用了对象 A1,因此即使在 a1 中没有引用它也不会被垃圾回收。

      【讨论】:

      猜你喜欢
      • 2011-11-07
      • 1970-01-01
      • 1970-01-01
      • 2010-12-13
      • 2015-04-30
      • 2011-02-25
      • 1970-01-01
      • 2011-01-06
      相关资源
      最近更新 更多