如果Java有垃圾回收机制,为什么会出现OutOfMemoryError错误?

7

2
如果你不把垃圾放在外面,那么收集员就无法把它带走。同样的道理也适用于Java,如果由于某些原因你没有释放对象的引用,那么它们就不会被垃圾收集器回收。这被称为内存泄漏。 - Luis
1
垃圾回收并不意味着您拥有“无限”的内存。 - Louis Wasserman
Stack Overflow是否允许仅接受一个答案?我尝试接受所有答案,但只有一个被选中。 - Prasad Kharkar
@PrasadKharkar,是的,只有一个被接受的答案。如果它们都同样好,那么我会接受第一个回答并点赞其他答案。 - earcam
是的,我也做了同样的事情 :) - Prasad Kharkar
7个回答

12

如果内存中没有任何对象符合垃圾收集条件,就会发生OutOfMemoryError错误。例如:

List<MyClass> hellos = new ArrayList<>();
for (;;) {
   hellos.add(new MyClass());
}

这会创建一个列表,并持续将MyClass对象添加到其中,直到内存耗尽。由于所有这些对象都有引用,因此没有一个对象符合垃圾回收的条件。


完全忘记了无限循环会导致创建无限对象,并且所有对象都有来自堆栈的引用。谢谢您,先生 :) 这解决了我的疑惑。 - Prasad Kharkar
4
@PrasadKharkar - 你不需要一个无限循环。那只是一个简单的例子。你所需要的是一个问题,其中计算过程中可达数据量对于堆来说太大,在计算期间某个时刻会产生这种情况。 - Stephen C
@StephenC,要发生 OutOfMemoryError 错误,循环只需要运行足够长的时间来占用所有内存空间,不需要无限循环。 - Prasad Kharkar
2
@PrasadKharkar - 你甚至不需要循环!只需尝试分配一个非常大的数组。 - Stephen C

3

有时候,内存会以出乎意料的方式被隔离。请考虑以下字符串示例。

char[] chars      = new char[10_000_000];  // May need to adjust.
String string     = new String(chars);
chars             = null;
String substring  = string.substring(5_000_000);
string            = null; 

chars数组可能会被收集。由于substring包含对内部数组的引用,后跟其偏移量和范围,因此string内部的数组可能不会被收集。因此,即使仅使用和访问5 * 10 6个字符,也仍然保留了107个字符。

Java 1.7.0_06

似乎String.substring不再具有这种行为。在Java Performance Tuning Guide web site的一篇文章中,Mikhail Vorontsov报告说,在Java 1.7.0_06及更高版本中,String.substring始终创建一个新的String,独立于旧的StringString类不再具有offsetrange实例变量。创建一个大字符串,获取子字符串并丢弃原始字符串将不会使旧字符串的char []被隔离。

// Java 1.7.0_06 and up
char[] chars      = new char[10_000_000];  // May need to adjust.
String string     = new String(chars);
chars             = null;
String substring  = string.substring(5_000_000);
// Copies a portion of string's array to a new array.
string            = null;
// string's array is no longer reachable, and may be garbage collected.

这里,“string”将会在内存中可用,但不会被引用,对吗?这是因为我们正在对字符串对象执行操作,由于它是不可变的,一个新的字符串将会被创建,而之前的字符串将会丢失。 - Prasad Kharkar
1
不会,因为string是不可变的,所以不会有任何更改其内容的操作。 因此,JVM可以安全地使substring重用string内部的char[]。 然后,当将string设置为null时,除了数组本身之外,string内的所有变量都可以被收集; substring引用它。 - Eric Jablow

2

因为并不是所有的内存都是垃圾。


据我理解,垃圾内存是指程序中不能再被引用的对象所占用的内存。我的理解正确吗? - Prasad Kharkar
没错。如果你不断地请求更多可以被程序引用的内存,你最终会耗尽它。 - user207421

1
垃圾收集器处理的是不会被使用的内存。
然而,如果你分配了内存,并且该内存在作用域内,也就是说,垃圾收集器无法真正清理该内存。
当分配的内存超过VM允许的限制时,您会收到该异常。

1

如果一个对象仍然被运行代码引用,它就不能被回收。因此,如果有一种即使理论上也可以访问对象的方法,它就会占用内存。

顺便说一句,Java并没有消除关注内存的需要;它只是自动化了很多内存管理工作。你仍然需要尽自己的一份力来确保在对象的有用寿命结束后不会将它们藏起来……因为只要它们可以被访问(无论多么间接),它们就会占用无法被回收的内存。


哦,是啊,怎么忘了在无限循环中创建对象会创建多个对象,并且它们将具有来自堆栈的引用。谢谢@cHao,这帮了我 :) - Prasad Kharkar

0

在完成对象的工作后最好将其置空,以确保对象可供垃圾收集器使用,避免出现内存不足或内存泄漏等问题。


这并不是那么容易的。如果多个引用指向同一个对象,那该怎么办呢?在这种情况下,如果你将对象置空,就有可能出现悬空引用的问题!所以,将对象置空并不总是解决方案! - Narendra
将对象引用设置为 null 通常是完全浪费时间的。方法局部引用无论如何都会超出范围,而作为实例变量的引用在任意时间设置为 null 并不容易,除非它们真的应该是局部变量,在这种情况下它们也会超出范围... - user207421

0

如果你的内存用尽了,而且没有任何可以删除的变量或对象,那么就会引发异常。

因此,使用垃圾回收(Garbage collection)的最佳方法是:

如果某个变量不再使用,请显式地将其赋值为 null 值。


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