89

今日、私の同僚と私はfinal、ガベージ コレクションを改善するために Java でキーワードを使用する方法について話し合いました。

たとえば、次のようなメソッドを記述した場合:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

メソッド内で変数を宣言するfinalと、ガベージ コレクションが、メソッドの終了後にメソッド内の未使用の変数からメモリをクリーンアップするのに役立ちます。

これは本当ですか?

4

15 に答える 15

90

次の例は、最終的な値型のローカル変数ではなく、最終的な参照型のフィールドを使用する、わずかに異なる例です。

public class MyClass {

   public final MyOtherObject obj;

}

MyClass のインスタンスを作成するたびに、MyOtherObject インスタンスへの発信参照を作成することになり、GC はそのリンクをたどってライブ オブジェクトを探す必要があります。

JVM はマークスイープ GC アルゴリズムを使用します。これは、GC の「ルート」の場所にあるすべてのライブ参照 (現在のコール スタック内のすべてのオブジェクトなど) を調べる必要があります。各ライブ オブジェクトは、アライブとして「マーク」され、ライブ オブジェクトによって参照されるすべてのオブジェクトも、アライブとしてマークされます。

マーク フェーズの完了後、GC はヒープをスイープし、マークされていないすべてのオブジェクトのメモリを解放します (残りのライブ オブジェクトのメモリを圧縮します)。

また、Java ヒープ メモリが「若い世代」と「古い世代」に分割されていることを認識することも重要です。すべてのオブジェクトは、最初は若い世代 (「ナーサリ」と呼ばれることもあります) に割り当てられます。ほとんどのオブジェクトは存続期間が短いため、GC は若い世代から最近のガベージを解放することに積極的です。若い世代のコレクション サイクルを生き延びたオブジェクトは、処理頻度の低い古い世代 ("tenured 世代" とも呼ばれます) に移動されます。

だから、頭のてっぺんから、「いいえ、「最終」修飾子はGCがその負荷を軽減するのに役立ちません」と言うつもりです。

私の意見では、Java でメモリ管理を最適化するための最善の戦略は、偽の参照をできるだけ早く排除することです。使い終わったらすぐにオブジェクト参照に「null」を割り当てることで、これを行うことができます。

または、さらに良いのは、各宣言スコープのサイズを最小限に抑えることです。たとえば、1000 行のメソッドの先頭でオブジェクトを宣言し、そのオブジェクトがそのメソッドのスコープ (最後の右中括弧) を閉じるまで存続する場合、オブジェクトは実際よりもずっと長く存続する可能性があります。必要。

数十行程度のコードしかない小さなメソッドを使用すると、そのメソッド内で宣言されたオブジェクトはより迅速に範囲外になり、GC ははるかに効率的な方法でほとんどの作業を行うことができます。若い世代。絶対に必要でない限り、オブジェクトを古い世代に移動したくありません。

于 2008-11-20T21:46:09.230 に答える
38

ローカル変数を宣言してfinalもガベージ コレクションには影響しません。変数を変更できないことを意味するだけです。totalWeightマークされた変数を変更しているため、上記の例はコンパイルされませんfinal。一方、(doubleの代わりにDouble)プリミティブを宣言するfinalと、その変数を呼び出し元のコードにインライン化できるため、メモリとパフォーマンスが向上する可能性があります。クラスに人数が多い場合に使用しますpublic static final Strings

一般に、コンパイラとランタイムは可能な限り最適化します。コードは適切に記述し、トリッキーになりすぎないようにすることをお勧めします。final変数を変更したくない場合に使用します。簡単な最適化はコンパイラーによって実行されると想定してください。パフォーマンスやメモリーの使用が心配な場合は、プロファイラーを使用して実際の問題を判別してください。

于 2008-11-20T21:25:25.497 に答える
26

いいえ、そうではありません。

これは定数finalを意味するのではなく、参照を変更できないことを意味するだけです。

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

JVM が参照を変更する必要がないという知識に基づく小さな最適化 (参照が変更されたかどうかを確認する必要がないなど) があるかもしれませんが、心配する必要がないほど小さいものです。

Finalコンパイラの最適化ではなく、開発者にとって有用なメタデータと考えるべきです。

于 2008-11-20T21:23:36.267 に答える
17

明確にするためのいくつかのポイント:

  • 参照を無効にしても、GC には役立ちません。もしそうなら、それはあなたの変数がスコープを超えていることを示しています。1 つの例外は、オブジェクト ネポティズムの場合です。

  • Java では、現時点ではオンスタック割り当てはありません。

  • 変数を final と宣言するということは、(通常の条件下では) その変数に新しい値を割り当てることができないことを意味します。final はスコープについて何も言わないので、GC への影響については何も言いません。

于 2011-06-19T18:25:19.960 に答える
11

この場合の "final" 修飾子の使用や、GC への影響についてはわかりません。

しかし、私はあなたにこれを言うことができます: プリミティブではなくボックス化された値 (たとえば、double ではなく Double) を使用すると、それらのオブジェクトがスタックではなくヒープに割り当てられ、GC がクリーンアップしなければならない不要なガベージが生成されます。

ボックス化されたプリミティブは、既存の API で必要な場合、または null 許容のプリミティブが必要な場合にのみ使用します。

于 2008-11-20T21:13:29.657 に答える
5

最終的な変数は、最初の割り当て後に変更することはできません (コンパイラによって強制されます)。

これは、ガベージ コレクション自体の動作を変更しません。唯一のことは、これらの変数が使用されなくなったときに null にできないことです (これは、メモリが不足している状況でのガベージ コレクションに役立つ場合があります)。

final を使用すると、コンパイラは何を最適化するかについて仮定を立てることができることを知っておく必要があります。コードをインライン化し、到達できないことがわかっているコードを含めない。

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

println はバイトコードに含まれません。

于 2009-08-30T08:16:29.960 に答える
4

世代別ガベージ コレクタには、あまり知られていないコーナー ケースがあります。(簡単な説明については、benjismithによる回答を参照してください。より深い洞察については、最後の記事を参照してください)。

世代別 GC の考え方は、ほとんどの場合、若い世代だけを考慮する必要があるということです。ルートの場所で参照がスキャンされ、次に若い世代のオブジェクトがスキャンされます。このより頻繁なスイープの間、古い世代のオブジェクトはチェックされません。

さて、問題は、オブジェクトが若いオブジェクトへの参照を持つことが許可されていないという事実から生じます。存続期間の長い (古い世代の) オブジェクトが新しいオブジェクトへの参照を取得すると、その参照はガベージ コレクターによって明示的に追跡される必要があり (ホットスポット JVM コレクターに関する IBM の記事を参照)、実際に GC パフォーマンスに影響を与えます。

古いオブジェクトが新しいオブジェクトを参照できない理由は、古いオブジェクトがマイナー コレクションでチェックされないため、オブジェクトへの唯一の参照が古いオブジェクトに保持されている場合、マークされず、誤って参照されるためです。スイープ段階で割り当て解除されます。

もちろん、多くの人が指摘しているように、final キーワードはガベージ コレクターに実際には影響しませんが、このオブジェクトがマイナー コレクションを生き延びて古いヒープに移動した場合、参照が新しいオブジェクトに決して変更されないことが保証されます。

記事:

ガベージ コレクションに関する IBM: historyホットスポット JVMおよびperformance。これらは 2003/04 年にさかのぼるため、完全には有効ではないかもしれませんが、GC についての読みやすい洞察を提供します。

Sun on Tuning ガベージ コレクション

于 2008-11-20T22:26:37.607 に答える
3

finalローカル変数とパラメータについては、生成されるクラスファイルに違いがないため、実行時のパフォーマンスに影響を与えることはありません。クラスにサブクラスがない場合、HotSpotはそのクラスを最終的なものとして扱います(その仮定に違反するクラスがロードされた場合、後で元に戻すことができます)。finalメソッドはクラスとほとんど同じだと思います。final静的フィールドでは、変数を「コンパイル時定数」として解釈し、それに基づいてjavacによって最適化を実行できる場合があります。finalonフィールドを使用すると、JVMは発生前の関係を無視することができます。

于 2008-11-25T18:16:03.383 に答える
3

GCは到達不能な参照に作用します。これは「最終」とは何の関係もありません。これは、1回限りの割り当てのアサーションにすぎません。一部のVMのGCが「最終」を利用できる可能性はありますか?方法や理由がわかりません。

于 2008-11-20T21:56:43.563 に答える
1

すべてのメソッドと変数は、デフォルトでサブクラスでオーバーライドできます。サブクラスをスーパークラスのメンバーのオーバーライドから救いたい場合は、キーワード final を使用してそれらを final として宣言できます。たとえば final int a=10; final void display(){......} 、メソッドをfinalにすることで、スーパークラスで定義された機能が変更されないことが保証されます。同様に、最終変数の値は決して変更できません。最終変数は、クラス変数のように動作します。

于 2012-02-07T21:10:59.187 に答える
0

絶対に、メモリ管理の大きな利点をもたらすオブジェクトの寿命を短くする限り、最近、あるテストとメソッドレベルのローカル変数を持つ別のテストでインスタンス変数を持つエクスポート機能を調べました。負荷テスト中に、JVM は最初のテストで outofmemory エラーをスローし、JVM は停止しました。しかし、2 回目のテストでは、メモリ管理が改善されたため、レポートを正常に取得できました。

于 2012-04-27T18:05:38.290 に答える
0

ローカル変数を final として宣言することを好むのは、次の場合だけです。

  • いくつかの匿名クラスと共有できるように、それらを最終的にする必要があります(たとえば、デーモンスレッドを作成し、それを囲むメソッドからいくつかの値にアクセスさせます)

  • それらを最終的なものにしたい(たとえば、誤ってオーバーライドされるべきではない/されてはならない値)

それらは高速なガベージ コレクションに役立ちますか?
私の知る限り、オブジェクトへの強い参照がゼロの場合、オブジェクトは GC コレクションの候補になります。その場合も、すぐにガベージ コレクションされるという保証はありません。一般に、強い参照は、スコープ外になるか、ユーザーが明示的に null 参照に再割り当てすると死ぬと言われます。したがって、それらを final と宣言することは、メソッドが存在するまで参照が存在し続けることを意味します (そのスコープが明示的に絞り込まれない限り)。 final 変数を再割り当てできない (つまり、null に再割り当てできない) ためです。したがって、ガベージコレクションの「最終」に関して、不要な遅延が発生する可能性があるため、GCの候補になる時期を制御する範囲を定義する際には少し注意する必要があると思います。

于 2014-09-16T07:55:38.737 に答える
0

私が考えることができる唯一のことは、コンパイラが最終的な変数を最適化し、それらを定数としてコードにインライン化する可能性があるということです。したがって、メモリが割り当てられないことになります。

于 2008-11-20T21:21:52.433 に答える