今日、同僚と私は、ガベージコレクションを改善するためのJava)のfinal
キーワードの使用法について議論します。
たとえば、次のようなメソッドを記述する場合:
public Double doCalc(final Double value)
{
final Double maxWeight = 1000.0;
final Double totalWeight = maxWeight * value;
return totalWeight;
}
メソッドfinal
で変数を宣言すると、メソッドの終了後にガベージコレクションがメソッドの未使用の変数からメモリをクリーンアップするのに役立ちます。
これは本当ですか?
次に、少し異なる例を示します。最終的な値型のローカル変数ではなく、最終的な参照型のフィールドがあります。
public class MyClass {
public final MyOtherObject obj;
}
MyClassのインスタンスを作成するたびに、MyOtherObjectインスタンスへの発信参照を作成することになり、GCはそのリンクをたどってライブオブジェクトを探す必要があります。
JVMは、マークスイープGCアルゴリズムを使用します。これは、GCの「ルート」ロケーションにあるすべてのライブ参照を検査する必要があります(現在の呼び出しスタック内のすべてのオブジェクトと同様)。各生きているオブジェクトは生きていると「マーク」され、生きているオブジェクトによって参照されるオブジェクトも生きているとマークされます。
マークフェーズの完了後、GCはヒープをスイープし、マークされていないすべてのオブジェクトのメモリを解放します(残りのライブオブジェクトのメモリを圧縮します)。
また、Javaヒープメモリは「若い世代」と「古い世代」に分割されていることを認識することが重要です。すべてのオブジェクトは、最初は若い世代(「ほとんどのオブジェクトは寿命が短いため、GCは若い世代から最近のガベージを解放することに積極的です。オブジェクトが若い世代のコレクションサイクルを生き延びた場合、古い世代に移動します( 「終身世代」として)、より頻繁に処理されます。
したがって、頭の中で「いいえ、「最終」モディファイヤはGCのワークロード削減に役立たない」と言うつもりです。
私の意見では、Javaでのメモリ管理を最適化するための最善の戦略は、偽の参照をできるだけ早く排除することです。オブジェクト参照に「null」を割り当てるとすぐにできます。あなたはそれを使い終わった。
または、さらに良いことに、各宣言スコープのサイズを最小化します。たとえば、1000行のメソッドの先頭でオブジェクトを宣言し、そのメソッドのスコープ(最後の閉じ中括弧)が閉じるまでオブジェクトが生きている場合、オブジェクトは実際よりもずっと長く生き続ける可能性があります必要。
わずか数行のコードで小さなメソッドを使用する場合、そのメソッド内で宣言されたオブジェクトはより迅速に範囲外になり、GCははるかに効率的な環境でほとんどの作業を実行できます。若い世代。どうしても必要な場合を除き、オブジェクトを古い世代に移動することは望ましくありません。
ローカル変数final
を宣言してもガベージコレクションに影響はありません。これは、変数を変更できないことを意味するだけです。上の例は、totalWeight
とマークされた変数final
を変更しているため、コンパイルできません。一方、プリミティブ(double
の代わりにDouble
)final
を宣言すると、その変数を呼び出し元のコードにインライン化できるため、メモリとパフォーマンスの一部が発生する可能性があります。改善。これは、クラスに複数のpublic static final Strings
がある場合に使用されます。
一般に、コンパイラとランタイムは可能な限り最適化します。コードを適切に記述し、あまりにトリッキーにならないようにすることが最善です。変数を変更したくない場合は、final
を使用します。コンパイラーによって簡単な最適化が実行されると想定し、パフォーマンスやメモリーの使用が心配な場合は、プロファイラーを使用して実際の問題を判別してください。
いいえ、それは強調されていません。
final
は定数を意味するものではなく、単に参照を変更できないことを意味することに注意してください。
final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.
JVMが参照を変更する必要がないという知識に基づいて、多少の最適化が行われる場合があります(変更されたかどうかを確認する必要がないなど)が、気にしないほど小さなものです。
Final
は、コンパイラーの最適化としてではなく、開発者にとって有用なメタデータと考えるべきです。
片付けるべきポイント:
参照を無効にしても、GCには役立ちません。もしそうなら、それはあなたの変数がスコープを超えていることを示します。 1つの例外は、オブジェクトネポチズムの場合です。
Javaでは、スタック上での割り当てはまだありません。
変数finalを宣言すると、(通常の条件下では)その変数に新しい値を割り当てることができなくなります。 finalはスコープについて何も述べていないため、GCへの影響については何も述べていません。
さて、この場合の「最終」修飾子の使用、またはGCへの影響については知りません。
しかし、私はcanこれを教えてください:プリミティブではなくBoxed値(たとえば、doubleではなくDouble)を使用すると、スタックではなくヒープにオブジェクトが割り当てられ、GCが不要なガベージを生成しますクリーンアップする必要があります。
既存のAPIで必要とされる場合、またはnullableなプリミティブが必要な場合にのみ、ボックス化されたプリミティブを使用します。
最終的な変数は、最初の割り当て後に変更できません(コンパイラによって強制されます)。
これは、garbage collectionの動作を変更しません。唯一のものは、これらの変数が使用されなくなったときにnullにできないことです(メモリ不足の状況でガベージコレクションに役立つ場合があります)。
Finalを使用すると、コンパイラーは何を最適化するかについての仮定を立てることができます。コードをインライン化し、到達可能でないことがわかっているコードを含めない。
final boolean debug = false;
......
if (debug) {
System.out.println("DEBUG INFO!");
}
Printlnは、バイトコードに含まれません。
GCは到達不能な参照に基づいて動作します。これは、「最終」とは何の関係もありません。「最終」は、1回限りの割り当ての単なる主張です。一部のVMのGCが「最終」を利用できる可能性はありますか?どうして、どうしてか分からない。
ローカル変数およびパラメーターのfinal
は、生成されるクラスファイルに影響を与えないため、ランタイムのパフォーマンスに影響を与えることはできません。クラスにサブクラスがない場合、HotSpotはそのクラスを最終的なものとして扱います(仮定を破るクラスがロードされると、後で元に戻すことができます)。メソッドのfinal
はクラスとほとんど同じだと思います。静的フィールドのfinal
を使用すると、変数を「コンパイル時定数」として解釈し、それに基づいてjavacで最適化を行うことができます。フィールドのfinal
を使用すると、JVMでhappens-before関係を無視する自由が得られます。
世代別のガベージコレクターではあまり知られていないコーナーケースがあります。 (簡単な説明については benjismith による回答をお読みください。より深い洞察は最後の記事をご覧ください)。
世代別GCの考え方は、ほとんどの場合、若い世代のみを考慮する必要があるということです。ルートの場所が参照用にスキャンされ、次に若い世代のオブジェクトがスキャンされます。このより頻繁なスイープでは、古い世代のオブジェクトはチェックされません。
現在、問題は、オブジェクトがより若いオブジェクトへの参照を持つことを許可されていないという事実に由来しています。長期間存続する(旧世代)オブジェクトが新しいオブジェクトへの参照を取得する場合、その参照はガベージコレクターによって明示的に追跡する必要があります(IBMの hotspot JVMコレクター に関する記事を参照)。実際にはGCに影響します。パフォーマンス。
古いオブジェクトが若いオブジェクトを参照できない理由は、古いオブジェクトがマイナーコレクションでチェックされないため、オブジェクトへの唯一の参照が古いオブジェクトに保持されている場合、マークされず、間違っているためですスイープ段階で割り当て解除されました。
もちろん、多くの人が指摘しているように、finalキーワードはガベージコレクターに実際には影響しませんが、このオブジェクトがマイナーコレクションを生き残り、古いヒープになった場合、参照がより若いオブジェクトに変更されないことを保証します。
記事:
IBMのガベージコレクション: history 、 hotspot JVM および performance 。これらは2003/04にさかのぼるので、もはや完全に有効ではないかもしれませんが、GCのいくつかの読みやすい洞察を与えます。
Sun on ガベージコレクションのチューニング
さまよう推測である多くの答えがあるようです。真実は、バイトコードレベルでのローカル変数の最終修飾子はありません。仮想マシンは、ローカル変数がfinalとして定義されたかどうかを決して知りません。
あなたの質問への答えは強調されています。
すべてのメソッドと変数はデフォルトでサブクラスでオーバーライドできます。サブクラスをスーパークラスのメンバーのオーバーライドから保存したい場合、キーワードfinalを使用してサブクラスをfinalとして宣言できます。たとえば、__final int a=10;
_ final void display(){......}
メソッドをfinalにすると、スーパークラスで定義された機能が変更されることはありません。同様に、最終変数の値は変更できません。最終変数はクラス変数のように動作します。
ローカル変数をfinalとして宣言することを好むのは、次の場合のみです。
I haveそれらを最終クラスにして、匿名クラスと共有できるようにします(例:デーモンスレッドを作成し、囲んでいるメソッドから値にアクセスできるようにする)
I wantで最終的にします(例:誤ってオーバーライドされるべきではない/しないような値)
高速なガベージコレクションに役立ちますか?
AFAIKオブジェクトが強い参照を持たない場合、GCコレクションの候補になります。その場合も、オブジェクトがすぐにガベージコレクションされるという保証はありません。一般に、強い参照はスコープ外になるか、ユーザーが明示的にnull参照に再割り当てすると死ぬと言われます。したがって、それらを宣言すると、メソッドが存在するまで参照が存在し続けることを最終的に意味します(スコープが明示的に絞り込まれない限り)最終変数を再割り当てできないため、特定の内部ブロック{})。だから、w.r.t Garbage Collection 'final' mayは望ましくない遅延を導入すると思います。
私が考えることができる唯一のことは、コンパイラが最終変数を最適化して、定数としてコードにインライン化する可能性があるため、メモリが割り当てられないということです。
絶対に、オブジェクトの寿命を短くしてメモリ管理の大きなメリットを得る限り、最近、あるテストでインスタンス変数を持つエクスポート機能と、メソッドレベルのローカル変数を持つ別のテストを検討しました。負荷テスト中、JVMは最初のテストでoutofmemoryerrorをスローし、JVMは停止しました。しかし、2番目のテストでは、メモリ管理の改善によりレポートを正常に取得できました。