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
}
}
main()
現在、リンクされたスレッドの提案は、これらのオブジェクトへのポインタを保持することですが、GC は、オブジェクトがもう使用されていないことを伝えるのに十分スマートであるように見えます。最後の呼び出しの後にこれらのオブジェクトへの呼び出しを追加することもできますlogMemory()
が、それは手動の解決策です。オブジェクトをテストするたびに、最後の呼び出しの後に何らかの副作用トリガー呼び出しを行う必要があります。そうしないlogMemory()
と、一貫性のない結果が得られる可能性があります。 .
一般的なケースの解決策を探しています。System.out.println(mh.hashCode()+mh2.hashCode())
のように呼び出しを追加することを理解していますmain()
メソッドで十分ですが、いくつかの理由でこれは嫌いです。まず、上記のテストに外部依存性が導入されます。SOUT 呼び出しが削除されると、メモリ ロギング呼び出し中の JVM の動作が変わる可能性があります。2 つ目は、ユーザー エラーが発生しやすいことです。上記のテスト対象のオブジェクトが変更された場合、または新しいオブジェクトが追加された場合、ユーザーはこの SOUT 呼び出しも手動で更新することを忘れないでください。そうしないと、テストで矛盾を検出するのが困難になります。最後に、私はこのソリューションが印刷されることをまったく嫌います。JIT の最適化をよりよく理解することで回避できる不必要なハックのように思えます。最後まで、パトリシア・シャナハンの答えは合理的な解決策を提供します(出力がメモリの健全性を目的としていることを明示的に出力します)が、可能であれば避けたいと思います。
したがって、私の最初の解決策は、これらのオブジェクトを静的リストに格納し、メイン クラスの 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();
}
}
}
しかし、ここでは問題を 1 ステップだけオフロードしました。JIT が finalize メソッドのループを最適化し、これらのオブジェクトを保存する必要がないと判断した場合はどうなるでしょうか。確かに、Java 7 ではオブジェクトを単にメイン クラスに保持するだけで十分かもしれませんが、finalzie メソッドを最適化して取り除くことができないことが文書化されていない限り、JIT/GC がこれらのオブジェクトを早期に削除することを理論的に妨げるものはまだありません。私の finalize メソッドの内容に副作用はありません。
1 つの可能性は、ファイナライズ メソッドを次のように変更することです。
protected void finalize() throws Throwable {
int codes = 0;
for(Object o : loggedObjects) {
codes += o.hashCode();
}
System.out.println(codes);
}
私が理解しているように(そしてここで間違っている可能性があります)、呼び出しSystem.out.println()
はJITがこのコードを取り除くのを防ぎます。これは外部の副作用を持つメソッドであるため、プログラムに影響を与えませんが、削除されました。これは有望ですが、私がそれを助けることができれば、何らかの意味不明なものが出力されることを本当に望んでいません. JIT が呼び出しを最適化できない (またはすべきではない!) という事実は、JITSystem.out.println()
に副作用の概念があることを示唆しています。あちらへ。
だから私の質問:
- メインクラスのオブジェクトのリストを保持するだけで、それらがGCされるのを防ぐのに十分ですか?
.hashCode()
それらのオブジェクトをループして、ファイナライズ メソッドのような単純なものを呼び出すだけで十分ですか?- この方法で何らかの結果を計算して印刷するだけで十分ですか?
- JIT が認識している他のメソッド (のような
System.out.println
) はありますか?それは最適化して取り除くことはできません。または、さらに良いことに、JIT にメソッド呼び出し/コード ブロックを最適化しないように指示する方法はありますか?
*いくつかの簡単なテストで、私が疑ったように、JVM は通常、メイン クラスの finalize メソッドを実行せず、突然終了することが確認されました。JIT/GC は、ファイナライズ メソッドが存在するという理由だけでオブジェクトを GC するほどスマートではないかもしれませんが、それが実行されなくても、常にそうであるとは確信していません。それが文書化された動作ではない場合、たとえそれが現在真実であっても、それが真実であり続けるとは信じられません。