1

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 するほどスマートではないかもしれませんが、それが実行されなくても、常にそうであるとは確信していません。それが文書化された動作ではない場合、たとえそれが現在真実であっても、それが真実であり続けるとは信じられません。

4

2 に答える 2

1

はい、mh1その時点でのガベージコレクションは合法です。その時点で、変数を使用できる可能性のあるコードはありません。JVMがこれを検出できた場合、対応するMemoryHogオブジェクトは到達不能として扱われます...その時点でGCが実行された場合。

オブジェクトの収集を禁止するには、後で like を呼び出すSystem.out.println(mh1)だけで十分です。「計算」で使用する場合も同様です。例えば

    if (mh1 == mh2) { System.out.println("the sky is falling!"); }

オブジェクトのリストをメイン クラスに保持するだけで、オブジェクトが GC されるのを防ぐことができますか?

リストが宣言されている場所によって異なります。リストがローカル変数で、以前mh1に到達不能になった場合、オブジェクトをリストに入れても違いはありません。

これらのオブジェクトをループして、ファイナライズ メソッドで .hashCode() のような単純なものを呼び出すだけで十分ですか?

メソッドが呼び出されるまでfinalizeに、GC はオブジェクトが到達不能であると判断しています。メソッドがオブジェクトの削除を防ぐ唯一のfinalize方法は、オブジェクトを他の (到達可能な) データ構造に追加するか、(到達可能な) 変数に割り当てることです。

JIT が認識している他のメソッド (System.out.println など) はありますが、最適化して取り除くことはできません。

うん...オブジェクトに到達可能にするものなら何でも。

またはさらに良いことに、メソッド呼び出し/コードブロックを最適化しないように JIT に指示する方法はありますか?

それを行う方法はありません...メソッド呼び出しまたはコードブロックが、実行中の計算に貢献する何かを行うことを確認することを除けば。


アップデート

まず、ここで行われているのは実際には JIT 最適化ではありません。むしろ、JIT は、ローカル変数(つまり、スタック上の変数) がいつ死んでいるかを判断するために GC が使用しているある種の「マップ」を発行しています ... プログラムカウンター (PC) に応じて。

コレクションを禁止する例はすべて、SOUT を介して JIT をブロックすることを含みます。

ねえ...物事がガベージコレクションされる正確なタイミングに依存するものはすべてハックです。適切に設計されたアプリケーションでそれを行うことは想定されていません。

コードを更新して、オブジェクトを保持しているリストがメイン クラスの静的変数であることを明確にしましたが、JIT が十分にスマートであれば、メイン メソッドがそれらを必要としないことがわかったら、理論的にこれらの値を GC できるようです。 .

同意しません。static実際には、JIT は aが決して参照されないと判断することはできません。次のケースを検討してください。

  • JIT が実行される前は、何も使用されないように見えますstatic s。JIT の実行後、アプリケーションは を参照する新しいクラスをロードしますs。JIT が変数を「最適化」したs場合、GC はそれを到達不能として扱い、nullそれまたはダングリング参照を作成します。動的にロードされたクラスsがそれを見ると、間違った値が表示されます...またはさらに悪いことです。

  • アプリケーションまたはアプリケーションが使用するライブラリがリフレクションを使用している場合static、JIT によって検出されなくても変数の値を参照できます。

したがって、この最適化を行うことは理論的には可能ですが、少数のケースがあります。

  • ほとんどの場合、できません。
  • 可能な場合はほとんどありませんが、(パフォーマンスの向上という点で) 見返りはほとんどありません。

同様にコードを更新して、メイン クラスの finalize メソッドについて話していることを明確にしました。

次の理由により、メイン クラスの finalize メソッドは無関係です。

  • メイン クラスのインスタンスを作成していない。
  • finalize メソッドは、別のメソッド (mainメソッドなど) のローカル変数を参照できません。

...それが存在することで、JIT が私の静的リストを無効にすることを防ぎます。

違います。staticとにかく、リストを核にすることはできません。上記を参照。

私が理解しているように、SOUT には、JIT が認識している特別な何かがあり、そのような呼び出しを最適化することを妨げています。

について特別なことは何もありませんsout。計算の結果に影響を与えるのは私たちが知っていることであり、したがって、JIT合法的に最適化して取り除くことができないことを私たちは知っています。

于 2013-03-04T03:28:54.953 に答える
1

これはやり過ぎかもしれませんが、安全で合理的​​に単純な計画です。

  • オブジェクトへの参照のリストを保持します。
  • 最後に、hashCode() の結果を合計してリストを反復処理します。
  • ハッシュ コードの合計を出力します。

合計を出力すると、最終ループが最適化されないことが保証されます。オブジェクトを作成するたびに行う必要があるのは、それを List add 呼び出しに入れることだけです。

于 2013-03-04T03:34:03.587 に答える