类似于可以防止 JIT 优化远离方法调用吗?我正在尝试跟踪长期存在的数据存储对象的内存使用情况,但是我发现如果我初始化一个存储,记录系统内存,然后初始化另一个存储,有时编译器(可能是 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()
,否则我可能会得到不一致的结果.
我正在寻找一般案例解决方案;我知道System.out.println(mh.hashCode()+mh2.hashCode())
在末尾添加一个电话main()
方法就足够了,但我不喜欢这个有几个原因。首先,它引入了对上述测试的外部依赖——如果删除了 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 处理?
- 循环遍历这些对象并
.hashCode()
在 finalize 方法中调用一些微不足道的东西就足够了吗? - 用这种方法计算和打印一些结果是否足够?
- JIT 是否知道其他方法(例如
System.out.println
)无法优化掉,或者更好的是,有没有办法告诉 JIT 不要优化掉方法调用/代码块?
*一些快速测试证实,正如我所怀疑的,JVM 通常不会运行主类的 finalize 方法,它会突然退出。JIT/GC 可能仍然不够聪明,无法仅仅因为 finalize 方法存在而对我的对象进行 GC,即使它没有运行,但我不确定情况总是如此。如果它没有记录在案的行为,我不能有理由相信它会保持真实,即使它现在是真实的。