考虑以下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实现中?如果没有,是否存在技术上的原因导致这种机制不起作用?
答案 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?