【发布时间】:2013-03-04 02:55:34
【问题描述】:
类似于Can JIT be prevented from optimising away method calls? 我正在尝试跟踪长期数据存储对象的内存使用情况,但是我发现如果我初始化一个存储,记录系统内存,然后初始化另一个存储,有时是编译器(大概是 JIT)足够聪明,可以注意到不再需要这些对象。
public class MemTest {
public static void main(String[] args) {
logMemory("Initial State");
MemoryHog mh = new MemoryHog();
logMemory("Built MemoryHog");
MemoryHog mh2 = new MemoryHog();
logMemory("Built Second MemoryHog"); // by here, mh may be GCed
}
}
现在链接线程中的建议是保留指向这些对象的指针,但 GC 似乎足够聪明,可以告诉 main() 不再使用这些对象。我可以在最后一次 logMemory() 调用之后添加对这些对象的调用,但这是一个相当手动的解决方案 - 每次我测试一个对象时,我都必须在最后一次 logMemory() 调用之后进行某种副作用触发调用,否则我可能会得到不一致的结果。
我正在寻找一般案例解决方案;我知道在 main() 方法的末尾添加像 System.out.println(mh.hashCode()+mh2.hashCode()) 这样的调用就足够了,但我不喜欢这个有几个原因。首先,它引入了对上述测试的外部依赖——如果删除了 SOUT 调用,则 JVM 在内存记录调用期间的行为可能会发生变化。第二,容易出现用户错误;如果上面测试的对象发生变化,或者添加了新对象,用户必须记得手动更新这个 SOUT 调用,否则他们会在测试中引入难以检测的不一致。最后,我根本不喜欢这个解决方案的打印——这似乎是一个不必要的黑客攻击,我可以通过更好地理解 JIT 的优化来避免。最后一点,Patricia Shanahan 的回答提供了一个合理的解决方案(明确打印输出是出于内存健全的目的),但如果可能的话,我仍然想避免它。
所以我最初的解决方案是将这些对象存储在一个静态列表中,然后在主类的 finalize 方法*中对它们进行迭代,如下所示:
public class MemTest {
private static ArrayList<Object> objectHolder = new ArrayList<>();
public static void main(String[] args) {
logMemory("Initial State", null);
MemoryHog mh = new MemoryHog();
logMemory("Built MemoryHog", mh); // adds mh to objectHolder
MemoryHog mh2 = new MemoryHog();
logMemory("Built Second MemoryHog", mh2); // adds mh2 to objectHolder
}
protected void finalize() throws Throwable {
for(Object o : objectHolder) {
o.hashCode();
}
}
}
但现在我只解决了一个问题——如果 JIT 优化了 finalize 方法中的循环,并决定不需要保存这些对象怎么办?诚然,对于 Java 7 来说,也许简单地将对象保存在主类中就足够了,但除非有文档证明 finalzie 方法无法被优化掉,否则理论上仍然没有任何东西可以阻止 JIT/GC尽早摆脱这些对象,因为我的 finalize 方法的内容没有副作用。
一种可能是将 finalize 方法更改为:
protected void finalize() throws Throwable {
int codes = 0;
for(Object o : loggedObjects) {
codes += o.hashCode();
}
System.out.println(codes);
}
据我了解(我在这里可能是错的),调用System.out.println() 将阻止 JIT 摆脱此代码,因为它是一种具有外部副作用的方法,因此即使它不会影响程序,无法删除。这是有希望的,但如果我能提供帮助,我真的不希望输出某种乱码。 JIT 不能(或不应该!)优化 System.out.println() 调用的事实向我表明 JIT 有副作用的概念,如果我可以告诉它这个 finalize 块有这样的副作用,它应该永远不要优化它。
所以我的问题:
- holdijng 是否是主类中的对象列表足以防止它们被 GC 处理?
- 循环遍历这些对象并在 finalize 方法中调用像
.hashCode()这样的微不足道的东西就够了吗? - 用这种方法计算和打印一些结果是否足够?
- JIT 是否知道其他方法(如
System.out.println)无法优化掉,或者更好的是,有什么方法可以告诉 JIT 不要优化掉一个方法调用/代码块?
*一些快速测试证实,正如我所怀疑的,JVM 通常不会运行主类的 finalize 方法,它会突然退出。 JIT/GC 可能仍然不够聪明,无法仅仅因为 finalize 方法存在而对我的对象进行 GC,即使它没有运行,但我不确定情况总是如此。如果它没有记录在案的行为,我不能有理由相信它会保持真实,即使它现在是真实的。
【问题讨论】:
-
将对象锚定在对象实例或静态变量中。或者只是在测量后以某种方式在
main中引用您的对象引用。 -
首先,这是我的第一个问题,够不够。第二,我在我的问题中说我想避免需要强制执行。
-
必须在某处引用对象。没关系,只要包含这些引用的任何内容本身都被引用(以及被引用的对象等),就可以是静态或堆栈引用。 (请注意,
main中的引用可以简单地创建一个 Object 数组并将引用放在那里——无需“使用”该对象。) -
制作 mh 和 mh2 静态变量。还能比这更简单吗?
-
我了解 GC 的工作原理。我的问题的目的是探索以一般和用户防错的方式防止 GC 处理我仍然感兴趣的内存使用的对象的最佳方法。将
mh和mh2设为静态会使前者失败,而强制在 main 方法中进行打印调用会使后者失败。
标签: java garbage-collection benchmarking jit