Java可以检测何时可以显式释放对象?

时间:2014-10-07 03:46:13

标签: java

考虑以下Java类:

class Main
{
    private static Integer AddOne(Integer i) {
        return i + 1;
    }
    public static void main(String[] args)
    {
        Integer one = new Integer(1);
        System.out.println(AddOne(one));
    }
}

在这个简单的示例中,很容易看到one引用的对象在调用AddOne后再也不会被引用。在main退出后的某个时间,它将被垃圾收集。但是,将对象放在内存中直到获得它是浪费 - 对Integer来说可能不是那么多,但如果我有一个大型数据结构(或许多),它可能会影响性能。 / p>

在我看来,编译器可以检查上面的代码,并且看到one的引用不可能转义Main类。然后,它可以在main函数的末尾显式解除分配/最终确定引用。

这样的工具是否存在于任何Java实现中?如果没有,是否存在技术上的原因导致这种机制不起作用?

2 个答案:

答案 0 :(得分:1)

这是不可能的(至少对于Java虚拟机而言),因为没有用于显式释放对象的JVM指令。 (指令集可以在Chapter 6 of the Java Virtual Machine Specification中找到。)让GC完成工作确实没有太多浪费。大多数实现都使用增量GC算法,这些算法非常适合注意事情是否很快就会超出范围。

我很确定编译器和JIT编译器非常擅长识别死变量,因此one在上次使用后通常会超出范围(以及Integer受GC限制) ,不管main()中发生了什么。

虽然在您的示例中,编译器可能会发现one引用的对象符合GC的条件,但这依赖于分析AddOne的操作以确保没有其他引用对象已创建。即使代码复杂性略有增加也会使这种静态分析几乎不可能。因此,即使编译器可以按照您的建议进行操作,我认为分析的复杂性会使尝试这样做变得毫无用处。

还有一个想法:如果您将one的初始化更改为:

Integer one = Integer.valueOf(1);

然后在Integer结束时释放main()绝对是错误的。这是因为Integer.valueOf(1)几乎肯定会返回由Integer类维护的缓存对象。由于缓存,在卸载Integer类定义之前(通常在JVM关闭期间),引用计数不会为0。

答案 1 :(得分:1)

Java垃圾收集的JIT和最新进展使得这类问题的确切答案变得困难。但是由于language specification的要求,JVM必须支持一些内容。

特别是,JVM不能垃圾收集实例变量(例如未使用的私有字段),即使它没有被字节码触及,因为调用者仍然可以使用反射来检查或访问该元素。

在你的例子中,我们讨论的是一个局部变量,它的处理方式不同。局部变量在堆栈上分配,而不是堆,并且垃圾收集器在方法返回后立即清除它(如果不是更快,实际上)。它不需要等到Main类被卸载。 Integer对象one所指的会比one本身保留更长时间,但只要它未被引用,GC就会在第一时间清理它。

我不知道目前这是否属实,但我认为编译器甚至可以重新设计这样的代码以完全避免Integer,而直接使用int。此示例中的new调用可能会明确地阻止此处,但替换Integer.valueOf()将避免显式分配请求,并可能允许编译器完全避免任何对象分配。

当然,在这个简单的例子中,GC或JIT不太可能有足够的时间来尝试清理任何东西。显然,你只是为了讨论而提供一个简单的例子,但值得一提的是,程序的持续时间会对GC或JIT的影响产生很大的影响,以及何时。

使用WeakReference建模/测试其中的一部分是可能的,如果我有时间,我会尝试用一些示例代码/分析重新回答这个答案。

您可能也很欣赏我去年提出的类似问题:Can unused private variables be GCed before their holding instance?