什么时候Java本地变量适合GC?

鉴于以下计划:

import java.io.*; import java.util.*; public class GCTest { public static void main(String[] args) throws Exception { List cache = new ArrayList(); while (true) { cache.add(new GCTest().run()); System.out.println("done"); } } private byte[] run() throws IOException { Test test = new Test(); InputStream is = test.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buff = new byte[256]; int len = 0; while (-1 != (len = is.read())) { baos.write(buff, 0, len); } return baos.toByteArray(); } private class Test { private InputStream is; public InputStream getInputStream() throws FileNotFoundException { is = new FileInputStream("GCTest.class"); return is; } protected void finalize() throws IOException { System.out.println("finalize"); is.close(); is = null; } } } 

当run方法中的while循环仍在执行且局部变量test仍在范围内时,你会期望调用finalize吗?

更重要的是,这种行为是否定义在任何地方? Sun是否有任何声明它是实现定义的?

这与之前询问此问题的方式相反,人们主要关注的是内存泄漏问题。 在这里,我们让GC积极地GCing一个我们仍然感兴趣的变量。你可能会期望,因为测试仍在“范围内”,它不会是GC。

对于记录,似乎有时测试“工作”(即最终命中OOM),有时它失败,这取决于JVM实现。

没有捍卫这个代码编写BTW的方式,这只是一个工作中出现的问题。

虽然如果对象仍然在范围内,它将不会被垃圾收集,但如果变量实际上没有在代码中进一步使用(因此您看到的行为不同),JIT编译器可能会将其从范围中取出,即使你阅读源代码,变量似乎仍然“在范围内”。

我不明白为什么你关心如果一个对象被垃圾收集,如果你不再在代码中引用它,但如果你想确保对象留在内存中,最好的方法是直接在一个类的字段中引用它们,甚至在静态领域更好。 如果静态字段引用该对象,则不会收集垃圾。

编辑: 这是您正在寻找的明确文档。

>我假设一个对象本地引用超出范围之前不能死亡。

这不能假设。 Java规范和JVM规范都不能保证这一点。

仅仅因为变量在范围内,并不意味着它指向的对象是可达的。 通常情况下,范围内变量指向的对象是可访问的,但是您的情况不是这样。 编译器可以在jit时确定哪些变量已经死亡,并且在oop-map中不包含这些变量。 由于“nt”指向的对象可以从任何实时变量到达[sic – 应该不能],因此它有资格进行收集。

我建议你和你的同事阅读关于垃圾收集的真相 。

在一开始,它说:

Java平台的规范很少有关于垃圾收集实际工作方式的承诺。 [消隐]

虽然看起来很混乱,但垃圾收集模型没有严格定义的事实实际上是重要且有用的 – 严格定义的垃圾收集模型可能无法在所有平台上实现。 同样,它可能会排除有用的优化并长期损害平台的性能。

在您的示例中, test变量在while循环中变为“不可见”(参见上面的A.3.3)。 此时,一些JVM将继续将变量视为包含“硬引用”,而其他JVM将视为变量已被置零。 对于兼容的JVM,这两种行为都是可接受的

引用JLS第3版(第12.6.1节第2段):

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

请注意,可访问性根本没有根据范围定义。 引用文本如下:

可以设计优化程序的转换,以减少可达到的对象的数量,使其少于可以被认为可达的对象的数量。 例如,编译器或代码生成器可以选择设置将不再用于null的变量或参数,以使得此类对象的存储可能更快地被回收。

(我强调补充说。)这意味着对象对象可能被垃圾收集,并且最终化可能比您预期的更早或更晚发生。 值得注意的是,在完成无法访问的对象之前,某些JVM需要多个GC周期。

最重要的是,依赖于早期或晚期发生的最终化的程序本质上是不可移植的,并且在我看来是错误的。

稍微偏离主题,但finalize()永远不应该用于关闭()文件。 该语言不保证finalize()将被调用。 始终使用try … finally结构来保证文件关闭,数据库清理等。

你觉得你觉得奇怪的是什么? 每次执行run()时,都会创建一个新的Test实例。 运行完成后,该测试实例超出范围并符合垃圾回收条件。 当然“符合垃圾收集条件”和“垃圾收集”不是一回事。 我希望如果你运行这个程序,你会看到一堆终结消息作为run complete的调用滚动。 作为我看到的唯一控制台输出是这些消息,当您看到每条消息时,我看不出您将如何知道哪个Test实例正在最终确定。 如果在每次运行调用开始时添加了println,可能会得到更有趣的结果,甚至可能会在Test对象中添加一个计数器,每次创建一个新对象时都会递增,并使用finalize消息输出。 然后你可以看到真正发生的事情。 (好吧,也许你用调试器来运行它,但这也可能会模糊不清。)

由于test仅使用一次,因此可以在调用后立即将其删除。 即使每次调用read使用了对getInputStream的调用而不是使用local is变量,也可以优化对象的使用。 由于使用锁定, FIleInputStream无法提前完成。 终结者很难。

无论如何,你的终结者毫无意义。 无论如何,底层的FileInputStream将在完成时自行关闭。

理论上,测试不能在范围内,因为它位于方法级别run(),并且当你从方法中出来时,局部变量应该被垃圾收集。但是你将结果存储在列表中,我已经读过它了该列表易于存储不易被垃圾收集的弱引用(取决于jvm实现)。