public class Test {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
int i=0;
for (;;) {
books.add(new Book("book"));
System.out.println(i++);
}
}
}
class Book {
private String name;
public Book(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
int i=0;
for (;;) {
books.add(new Book("book"));
}
}
}
class Book {
private String name;
public Book(String name) {
this.name = name;
}
}
为什么结果不同?
OutOfMemoryError
区别在于速度。
它们最终都会耗尽内存:你正在制作一个占用一些空间的东西(即一本书),并将其添加到一个数组列表(它本身也占用一些空间),从而确保你制作的这个东西不能被垃圾收集。你就这样一直循环下去。
一个快速而另一个慢得多的原因是书籍实例不会占用那么多空间,并且
System.out.println
通过一段疯狂的旅程发送数据,最终将字符发送到IDE 中的控制台视图。 println waits 等待着疯狂的旅程 - 它和整个过程一样慢,而且这个过程并不是特别有效。换句话说,System.out.println
或多或少相当于Thread.sleep(10L)
(没有让CPU休息的好处)。它比不需要等待字符出现在 IDE 控制台视图中的代码慢得多。
因此,没有 sysout 语句的代码片段运行速度要快得多,因此,其“每分钟使用的内存”率要大一个数量级。因此,那个片段很快就会因 OutOfMemoryError 崩溃,另一个片段也会这样做,但需要更长的时间。
替代解释:
我真的怀疑这就是正在发生的事情,但是,从理论上讲,也许是可能的:
JVM 进行热点编译。 JVM 进行高级优化(例如,解释它做什么、如何做以及何时做,需要一千页来解释),但仅限于运行大量的代码(想法是:99% 的时间都花在1% 的代码,因此,唯一需要这种处理的代码是那 1%;分析 100% 的代码不是一个好主意,因为这种分析非常昂贵)。没有 sysout 的代码片段连续运行,因此 CPU 非常繁忙,因此,就热点而言,JVM 的行为是不同的。 sysout 端为 JVM 提供了更多的空闲时间来用于早期分析。
这样的分析
可能确定列表从未被使用,因此,书籍实例未被使用,因此,可以消除该代码。 规则表明这是允许的(不保证“OutOfMemoryError”),但是,我怀疑这种情况会发生。解决这个问题的一种简单方法是使 for 循环条件不是无限的(例如,“只要
System.currentTimeMillis()
为正就循环”,或者循环 100 万亿次,从技术上讲这不是无限的 - 第二个是更好,System.currentTimeMillis()
很贵),然后做一些需要书籍仍然存在的事情,从而确保任何热点分析都无法确定“呃,没有人使用列表或书籍,所以我可以完全消除这些行”。