如何在Java中正确删除数组

13

我接触Java才4天,从搜索到的教程来看,讲师们花了很多精力解释如何分配二维数组(例如):

Foo[][] fooArray = new Foo[2][3];

...但我没有找到任何解释如何删除它们的内容。

从内存角度来看,变量fooArray将指向堆中的一块内存,其中有两个元素。每个元素也指向堆中的另一个块,该块有三个元素。

也就是说,我只需要引用第一个元素块,垃圾回收器会完成工作吗?

Foo[1] = null;Foo[2] = null;

还是我必须将每个实例化的Foo元素都设为null呢?

Foo[1][1] = null; Foo[1][2] = null; Foo[1][3] = null; ...


1
@TT。虽然答案是一样的,但我的问题是关于数组的特定问题。这意味着,即使我之前已经阅读过相关内容,我仍然会从一个新手的角度产生疑问。 - Chronus
5
我明白你的意思。除了基本数据类型(例如 int、double 等),其他所有东西都是对象。这一点很重要。 - TT.
3个回答

17

说明

在Java中,你无法明确地删除某些东西。这是垃圾收集器的工作。它将删除任何不再被任何人使用的东西。所以,要么

  1. 让变量超出范围,或者
  2. 将其分配给null
  3. 或任何其他实例。

然后,数组实例(以及其子数组)就不再被引用,垃圾收集器最终将删除它。


参考资料

要理解为什么重新分配外部数组就足以删除内部数组,你需要了解它们如何被引用。同样,垃圾收集器可以删除任何无法访问的东西。因此,让我们看一个数组,例如:

int[][] outer = {{1, 2}, {3, 4}, {5, 6}};

我们有4个数组实例。其中一个是int[][]类型的,另外三个是int[]类型的。此外,我们还有一个变量outer。这些实例被引用如下:

                       ___> {1, 2}
                      |
outer  --> int[][] ---|---> {3, 4}
                      |
                      |___> {5, 6}

因此,通过删除 outer,就没有任何东西再引用 int[][] 了。垃圾回收器现在可以将其删除。但这也会删除对内部数组的所有引用,因此垃圾回收器现在也可以将它们删除。

现在假设您将通过另一个变量引用其中一个内部数组:

int[][] outer = {{1, 2}, {3, 4}, {5, 6}};
int[] thirdInner = outer[2];
other = null; // remove the reference

现在的情况是

outer  --> null

                       ___> {1, 2}
                      |
           int[][] ---|---> {3, 4}
                      |
                      |______> {5, 6}
                          |
thirdInner _______________|

因此,垃圾回收器现在将删除外部数组int[][],这也会删除对第一个和第二个内部数组的所有引用。但第三个数组仍由thirdInner引用,因此在垃圾回收后,我们有:

outer       --> null
thirdInner  --> {5, 6}

3
“未被引用”这个术语有些棘手——因为某些时候所有东西都是“未被引用”的。正确的说法应该是“无法通过GC根硬引用访问到”。 - Boris the Spider
@BoristheSpider 我认为“未被引用”是一个不错的选择,因为在Java中一切都是有引用的,只是可能不一定来自Java对象,而是来自JVM内部(例如堆栈变量、已加载的java.lang.Class实例等...)(后者被标记为GC根)。所以我仍然会称之为“已引用”。 - bwoebi
2
“未被引用”并不是严格正确的说法。“未被引用”意味着相互引用的两个对象或自我引用的一个对象不会被删除,这显然是不正确的。垃圾收集器不会删除“未被引用”的对象,它们会删除从GC根(运行线程)不可达的对象。当将null赋值给outer时,整个数组和所有内部数组都变得对程序不可达,并且一次性清除所有内容。没有必要先删除外部数组,才能发现内部数组也可以被删除。 - Boann
1
请随意更正术语。但我认为保持简单最好,然后添加一个部分,其中包含对一些更深入解释的参考,快速总结其工作原理的两个句子。 - Zabuzard
2
这个短语在第一部分作为介绍是可以的,但是我在第二部分编辑了句子,因为当术语与其他文献中的术语匹配时(如果读者对更多细节感兴趣),这对读者很有帮助。我还添加了一个链接到规范。我认为用“can”而不是“will”很重要,因为垃圾回收是有意非确定性的。实际上,“delete”这个术语甚至也不符合实际情况,但我认为最好保持这种方式,因为已经有足够的进一步阅读材料了。 - Holger
显示剩余2条评论

13

如果数组超出作用域后,没有其他引用,则垃圾回收器将回收该内存。

如果您希望在变量超出作用域之前将其引用设置为null(请记住,如果其他代码具有此引用,则它不会被垃圾回收):

Foo[][] fooArray = new Foo[2][3];

...

// this will null the reference to the array
fooArray = null;

3
“once”的措辞使得听起来好像一旦最后一个引用超出作用域,内存就会被回收。这是Java垃圾回收的一个相当重要的特性,却不是这种情况。 - Konrad Rudolph
好的观点 - 已编辑。 - Jason

2
与C语言不同,Java提供了自动垃圾回收机制,当数组变得无法访问(即超出作用域)时会自动清除它。如果您想要的话,可以将数组设置为空(null),使其所在的内存位置变得无法访问。"最初的回答"。
    Foo[][] fooArray = new Foo[2][3];
    .
    .
    .
    fooArray = null;
    System.gc();

这个gc调用并不能确保JVM运行垃圾回收,但它建议Java虚拟机花费精力回收未使用的对象,以便使它们当前占用的内存可用于快速重用。当方法调用返回时,Java虚拟机已尽最大努力从所有丢弃的对象中回收空间。

Original Answer翻译成"最初的回答"


4
我不太看得出建议使用 System#gc 的好处。在大多数情况下,它只会降低性能。我们应该让垃圾回收器自己去完成其工作。通常只有在测量前(例如分析器和其他工具)清理一些东西时才会使用它。 - Zabuzard
我同意你的看法,但在某些情况下,建议JVM立即进行完整的垃圾回收可能是有意义的,因为你知道应用程序在重负工作之前将会闲置几分钟。 - Vaibhav Gupta
5
垃圾回收器不会**清除(clear)**数组,仅仅释放它。恶意软件有可能访问数据,有时会一直存在,直到内存被重新使用。如果数组包含敏感数据,在释放之前应主动进行清除。 - Jonathan Rosenne

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