Java中垃圾收集器的工作

3
在调用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,它是否成为垃圾?

我认为在调用System.gc()时,a1a2mas因为状态为null而可以进行垃圾回收,但是我可能错了。


相关:https://dev59.com/pXbZa4cB1Zd3GeqPMPxp#18930323 - Marko Topolnik
5个回答

8
首先,程序变量永远不会被“垃圾收集”,只有对象才会被GC收集,程序变量是指向对象的“引用”,而不是对象本身。(在提及Java变量时的常见术语混淆了这一点。)
其次,a1a2mas所引用的任何对象都不会被GC回收。尽管a1a2已经设置为null,但还是可以通过listmas访问对这些对象的引用。为什么即使调用clear()方法后,由a2引用的对象仍然可以通过mas访问呢?因为你无法通过将变量mas传递到clear()方法来更改main()中的局部变量。在该方法中,你正在更改形式参数(也称为mas),它是与main()中的局部变量不同的变量。Java严格按值传递。但是,当涉及到对象时,总是传递一个“引用”。
通常,规则是只有当对象从任何程序变量(直接或间接)不可访问时,才会对对象进行GC回收。

我承认这是工作中的测试任务之一。很可能我不会发送它,但我对它的工作原理非常感兴趣。谢谢你的解释。如果您推荐一篇有趣的文章或书籍,我也想了解Java的复杂性和工作原理。 - ip696
@ip696 - 我总是建议从Java教程开始。它们准确而且非常详尽。 - Ted Hopp

2

没有一个符合GC的条件:

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

下面是在调用GC时内存中的对象示例:

enter image description here


2

目前没有可供GC回收的内容:

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

请注意,您的方法

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

这个方法并没有实现任何功能。

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

有关更多解释,请参见Java是“按引用传递”还是“按值传递”?


1
唯一关于对象可达性的规则是(§12.6.1):
可达对象是指可以从任何活动线程中的任何潜在连续计算中访问的任何对象。
检查您的代码,很明显没有对象可以在任何潜在的连续计算中被访问。在main完成后,对它们的任何引用都将不存在;此外,System.gc()是程序中的最后一个语句,该语句的计算不会访问这些或任何其他对象(在Java级别上根本没有语义)。因此,所有对象实际上在那时都是不可达的,无论垃圾回收的实际实现是否意识到这一点。

1
这并不是最后一条语句,因为该方法以// some code ...结束,很可能意味着在调用gc()之后还有更多的代码。即使没有其他代码,GC是否有足够的信息来知道没有代码跟随并且变量永远不会再次使用。我不是一个底层GC和JVM专家,但我认为它不能。如果你坚持规格说明,那么我认为你的回答是事实上正确的,但有点吹毛求疵,因为在实践中,据我所知,此时实际上没有任何东西适合进行GC处理。 - JB Nizet
我认为这是一个吹毛求疵的问题:它来自于一个作业/面试,显然要求一个权威的答案,直接从规范中得出。因此,它非常依赖于那个“一些代码”是什么。顺便说一句,当人们使用松散的推理来决定在某个时刻是否可能发生OOME时,这个问题就变得不那么吹毛求疵了。例如,即使是超出范围的变量也可以防止实际上无法访问的对象被GC回收(这是我当前答案的反面)。 - Marko Topolnik
2
好的。提醒我们规格是加1分。但考虑到OP还不理解引用如何传递给方法,而且他很可能正在面试入门级职位,如果面试官期望如此精确的答案,我会感到非常惊讶 :-) - JB Nizet
1
void x() { { long[] longs1 = new long[Integer.MAX_VALUE]; } System.gc(); long[] longs2 = new long[Integer.MAX_VALUE]; }请注意嵌套的 {} 块。在分配 longs2 的时候,longs1 显然是无法访问的,但这仍然可能导致 OOME。我曾经有机会对此进行详细处理:https://dev59.com/pGYr5IYBdhLWcg3wn7cc#13531615 - Marko Topolnik
即使没有代码跟随,GC 是否有足够的信息来知道没有代码跟随,变量将不再被使用:GC 间接受益于 JIT 编译器,后者确实会进入细节,了解在哪里使用了什么。它会生成积极重用堆栈/寄存器位置的代码,并且每个安全点都有一个“oopmap”,详细说明可能活动对象指针的位置。这就是为什么我的答案不仅仅是学术小贴士的原因。 - Marko Topolnik
显示剩余2条评论

0

当您调用System.gc()时,由new A("a1")new A("a2")mas创建的对象并未被垃圾回收。

任何被线程直接或间接引用的对象都不会被垃圾回收。该线程正在运行此方法,因此局部变量将被保留。

以使用new A("a1")创建的对象(我们称之为A1)为例。

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


让我们在聊天中继续讨论。我已经删除了我在这里发表的其他评论,因为它们不再适用或与此问答无关。 - cfi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接