对象在Java和.Net中的寿命
本文关键字:Net Java 对象 | 更新日期: 2023-09-27 18:20:06
我正在读取"CLR via C#",在本例中,最初分配给"obj"的对象在执行第1行之后(而不是第2行之后)将有资格进行垃圾回收。
void Foo()
{
Object obj = new Object();
obj = null;
}
这是因为局部变量寿命不是由定义它的范围定义的,而是由您上次阅读它的时间定义的
所以我的问题是:Java呢?我写了这个程序来检查这种行为,看起来对象是活的。我认为JVM在解释字节码时不可能限制变量的生存期,所以我尝试用"java-Xcomp"运行程序来强制编译方法,但无论如何都没有调用"finalize"。Java似乎不是这样,但我希望我能在这里得到一个更准确的答案。另外,Android的Dalvik虚拟机呢?
class TestProgram {
public static void main(String[] args) {
TestProgram ref = new TestProgram();
System.gc();
}
@Override
protected void finalize() {
System.out.println("finalized");
}
}
已添加:Jeffrey Richter在"CLR via C#"中给出了代码示例,如下所示:
public static void Main (string[] args)
{
var timer = new Timer(TimerCallback, null, 0, 1000); // call every second
Console.ReadLine();
}
public static void TimerCallback(Object o)
{
Console.WriteLine("Callback!");
GC.Collect();
}
如果项目目标为"Release"(GC.Collect()调用后计时器被破坏),则TimerCallback在MS.Net上只调用一次,如果目标为"Debug"(变量寿命增加,因为程序员可以尝试使用调试器访问对象),则每秒钟调用一次。但在Mono上,无论您如何编译,回调都会每秒调用一次。看起来Mono的"Timer"实现将对实例的引用存储在线程池中的某个位置。MS实现不能做到这一点。
请注意,仅仅因为对象可以被收集,并不意味着它实际上会在任何给定点被收集,因此您的方法可能会给出假阴性。如果调用了任何对象的finalize
方法,你可以肯定地说它是不可访问的,但如果没有调用该方法,你就无法从逻辑上推断出任何东西。与大多数GC相关的问题一样,垃圾收集器的不确定性使得很难对它将要做的事情进行测试/保证
关于可达性/可收集性的主题,JLS表示(12.6.1):
可达对象是指可以在任何潜在的持续计算中从任何活动线程访问的任何对象。程序的优化转换可以设计为将可访问的对象数量减少到少于那些天真地被认为是可访问的。例如,编译器或代码生成器可以选择将不再使用的变量或参数设置为null,以使此类对象的存储可以更快地回收。
这或多或少正是你所期望的——我认为上面的段落与"一个对象是不可访问的,如果你肯定不会再使用它"同构。
回到您最初的情况,您能想到在第1行之后与第2行之后被视为不可访问的对象之间有什么实际的后果吗?我最初的反应是没有,如果你以某种方式找到了这样的情况,那很可能是糟糕/扭曲的代码导致VM挣扎,而不是语言中固有的弱点。
尽管我对反驳持开放态度。
编辑:谢谢你的有趣的例子。
我同意你的评估,并看看你要去哪里,尽管问题可能更多的是调试模式正在微妙地改变代码的语义。
在编写的代码中,您将Timer
分配给一个局部变量,该变量随后不会在其范围内读取。即使是最琐碎的转义分析也可以表明timer
变量在main
方法中的其他任何地方都没有使用,因此可以省略。因此,我认为您的第一行可以被认为完全等同于直接调用构造函数:
public static void Main (string[] args)
{
new Timer(TimerCallback, null, 0, 1000); // call every second
...
在后一种情况下,很明显,新创建的Timer
对象在构造后是不可立即访问的(假设它没有在构造函数中偷偷地做任何事情,比如将自己添加到静态字段等);以便GC一有空就收集。
现在,在调试情况下,情况略有不同,因为您提到的原因是开发人员可能希望稍后在方法中检查局部变量的状态。因此编译器(和JIT编译器)无法优化这些;就好像在方法的末尾有一个对变量的访问,在这之前阻止收集。
即便如此,我认为这实际上并没有改变语义。GC的本质是收集很少得到保证(至少在Java中,你得到的唯一保证是,如果抛出OutOfMemoryError,那么所有被认为不可访问的东西都会立即被GCed)。事实上,假设您有足够的堆空间来容纳在运行时生存期内创建的每个对象,那么无操作GC实现是完全有效的。因此,虽然你可能会观察到CCD_ 6滴答作响的次数的行为变化,这很好,因为根据调用它的方式,无法保证你会看到什么
在这一点上,我让你回到这个答案的第一句话
Java通常具有这样的行为,即如果对象在作用域中是可访问的(有一个对它的引用不是垃圾),那么该对象就不是垃圾。这是递归的,因此如果a
是对引用了b
的对象的引用,则b
引用的对象不是垃圾。
在您仍然可以访问ref
引用的对象的范围内(您可以添加一行System.out.println(ref.toString())
),ref
不是垃圾。
然而,根据Sun网站上的这个旧消息来源,这在很大程度上取决于JVM的具体实现。