类似于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()
调用后添加对这些对象的调用,但这是一个相当手动的解决方案 - 每次测试一个对象时,我都必须在最后{{1}之后进行某种副作用触发调用调用,或者我可能得到不一致的结果。
我正在寻找一般案例解决方案;我了解在logMemory()
方法的末尾添加System.out.println(mh.hashCode()+mh2.hashCode())
之类的调用就足够了,但出于以下几个原因我不喜欢这个。首先,它引入了对上述测试的外部依赖 - 如果删除了SOUT调用,则内存日志记录调用期间JVM的行为可能会发生变化。其次,它容易出现用户错误;如果上面测试的对象发生变化,或者添加了新的对象,用户必须记住手动更新此SOUT调用,否则他们将在测试中引入难以检测的不一致性。最后,我不喜欢这个解决方案打印 - 这似乎是一个不必要的黑客,我可以通过更好地理解JIT的优化来避免。最后一点,Patricia Shanahan的回答提供了一个合理的解决方案(明确打印出输出是出于内存健全的目的)但我仍然希望尽可能避免使用它。
所以我的初始解决方案是将这些对象存储在静态列表中,然后在主类的finalize方法*中迭代它们,如下所示:
main()
但是现在我只是将问题卸载了一步 - 如果JIT在finalize方法中优化了循环,并决定不需要保存这些对象怎么办?不可否认,也许简单地将主题类中的对象保存在Java 7中已经足够了,但除非有文档证明finalzie方法无法优化,否则理论上仍然没有 阻止JIT / GC获取尽早摆脱这些对象,因为我的finalize方法的内容没有任何副作用。
一种可能性是将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();
}
}
}
据我了解(我可能在这里错了),调用protected void finalize() throws Throwable {
int codes = 0;
for(Object o : loggedObjects) {
codes += o.hashCode();
}
System.out.println(codes);
}
将阻止JIT摆脱此代码,因为它是一种具有外部副作用的方法,因此即使它不会影响该程序,它无法删除。这很有希望,但如果我能提供帮助的话,我真的不想要输出某种胡言乱语。 JIT不能(或不应该!)优化System.out.println()
次调用的事实告诉我,JIT有一个副作用的概念,如果我能告诉它这个最终化块有这样的副作用,它永远不应该优化它。
所以我的问题:
System.out.println()
这样的微不足道的东西?.hashCode()
)无法优化,或者甚至更好,是否有某种方法可以告诉JIT不要优化方法调用/代码块?< / LI>
*正如我所怀疑的,一些快速测试确认JVM通常不会运行主类的finalize方法,它会突然退出。 JIT / GC可能仍然不够智能GC我的对象只是因为finalize方法存在,即使它没有运行,但我不相信总是如此。如果没有记录的行为,我无法合理地相信它会保持正确,即使现在也是如此。
答案 0 :(得分:1)
是的,mh1
在那时被垃圾收集是合法的。此时,没有可能使用该变量的代码。如果JVM可以检测到这一点,那么相应的MemoryHog
对象将被视为无法访问...如果GC在该点运行。
稍后调用System.out.println(mh1)
就足以抑制对象的收集。所以会在“计算”中使用它; e.g。
if (mh1 == mh2) { System.out.println("the sky is falling!"); }
是否在主类中保留了一个对象列表,以防止它们被GCed?
取决于声明列表的位置。如果列表是局部变量,并且在mh1
之前变为无法访问,那么将对象放入列表将没有任何区别。
是否循环遍历这些对象并在finalize方法中调用像.hashCode()这样的微不足道的东西?
当调用finalize
方法时,GC已经确定该对象无法访问。 finalize
方法可以阻止对象被删除的唯一方法是将其添加到其他(可达)数据结构或将其分配给(可达)变量。
JIT是否知道其他方法(如System.out.println)无法优化,
是......使对象可以到达的任何东西。
甚至更好,有没有办法告诉JIT不要优化方法调用/代码块?
无法做到这一点......除了确保方法调用或代码块执行有助于执行计算的内容之外。
<强>更新强>
首先,这里发生的事情并不是真正的JIT优化。更确切地说,JIT正在发出某种“地图”,GC用它来确定何时局部变量(即堆栈上的变量)已经死...取决于程序计数器(PC)。
你禁止收集的例子都涉及通过SOUT阻止JIT,我想避免这种有点愚蠢的解决方案。
嘿......任何事情都取决于什么时候收集垃圾的确切时间是一个黑客。你不应该在一个设计合理的应用程序中这样做。
我更新了我的代码,以清楚地表明持有我的对象的列表是主类的静态变量,但似乎JIT足够聪明,一旦它知道主要方法没有理论上它仍然可以GC这些值。需要他们。
我不同意。实际上,JIT无法确定永远不会引用static
。考虑以下情况:
在JIT运行之前,似乎没有任何内容会再次使用static s
。运行JIT后,应用程序将加载一个引用s
的新类。如果JIT“优化”s
变量,GC会将其视为无法访问,并null
或创建悬空引用。当动态加载的类然后查看s
时,它会看到错误的值......或者更糟。
如果应用程序...或应用程序使用的任何库...使用反射,那么它可以引用任何static
变量的值,而JIT不会检测到它。
因此,虽然理论上可以做到这一点,但优化的案例很少:
我同样更新了我的代码以澄清我正在谈论主类的finalize方法。
主类的finalize方法无关紧要,因为:
main
方法)。...它的存在阻止了JIT核对我的静态列表。
不正确。无论如何,static
列表无法被激活;见上文。
据我了解,JIT知道的SOUT有一些特别之处,可以阻止它优化这些调用。
sout
没有什么特别之处。这只是我们知道影响计算结果的因素,因此我们知道 JIT无法合法优化。
答案 1 :(得分:1)
这是一个可能过度的计划,但应该是安全且相当简单的:
打印总和可确保无法优化最终循环。您需要为每个对象创建做的唯一事情就是将它放在List add调用中。