Javaで変数にfinalを使用すると、ガベージコレクションが改善されますか?


86

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

たとえば、次のようなメソッドを作成する場合:

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

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

これは本当ですか?


実際、ここには2つのことがあります。1)メソッドのローカルフィールドインスタンスに書き込むとき。あなたは、インスタンスに書き込むときがある可能性が利益こと。
ユージーン

回答:


86

これは少し異なる例です。最終的な値型のローカル変数ではなく、最終的な参照型のフィールドがあります。

public class MyClass {

   public final MyOtherObject obj;

}

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

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

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

また、Javaヒープメモリは「若い世代」と「古い世代」に分割されていることを認識することが重要です。すべてのオブジェクトは、最初は若い世代(「保育園」と呼ばれることもあります)に割り当てられます。ほとんどのオブジェクトは短命であるため、GCは若い世代から最近のゴミを解放することに積極的です。オブジェクトが若い世代の収集サイクルを生き延びた場合、そのオブジェクトは古い世代(「テニュア世代」と呼ばれることもあります)に移動され、処理の頻度は低くなります。

ですから、頭から離れて、「いいえ、「最終的な」モディファイアはGCの作業負荷を軽減するのに役立ちません」と言います。

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

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

わずか12行程度のコードで小さなメソッドを使用する場合、そのメソッド内で宣言されたオブジェクトはより迅速にスコープから外れ、GCははるかに効率的な範囲内でほとんどの作業を実行できるようになります。若い世代。どうしても必要な場合を除いて、オブジェクトを古い世代に移動することは望ましくありません。


思考の糧。インラインコードの方が速いといつも思っていましたが、jvmのメモリが不足すると、速度も低下します。hmmmmm ...
WolfmanDragon

1
ここで推測しているだけです...しかし、JITコンパイラは、パフォーマンスを適度に向上させるために、(オブジェクトではなく)最終的なプリミティブ値をインライン化できると思います。一方、コードのインライン化は、大幅な最適化を行うことができますが、最終的な変数とは何の関係もありません。
benjismith 2008

2
おそらく、すでに作成した最終的なオブジェクトにはnullを代入することはできない、最終的に困難なものを作ることができ、代わりにヘルプの
エルナン・Echeを

1
{}を使用して、他のクラスメソッドに関連しない可能性のあるいくつかのプライベートメソッドにスコープを分割するのではなく、大きなメソッドのスコープを制限することもできます。
mmm 2012

可能な場合は、フィールドの代わりにローカル変数を使用して、メモリのガベージコレクションを強化し、参照の関係を減らすこともできます。
sivi 2014年

37

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

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


26

いいえ、それは明らかに真実ではありません。

それを忘れないでくださいfinal平均を一定にしない、それはちょうどあなたが参照を変更することはできませんを意味します。

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

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

Final コンパイラの最適化としてではなく、開発者にとって有用なメタデータとして考える必要があります。


17

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

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

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

  • 変数finalを宣言すると、(通常の状態では)その変数に新しい値を割り当てることができなくなります。finalはスコープについては何も述べていないため、GCへの影響については何も述べていません。


javaには(プリミティブとヒープ内のオブジェクトへの参照の)スタック上の割り当てがあります:stackoverflow.com/a/8061692/32453 Javaでは、匿名クラス/ラムダと同じクロージャ内のオブジェクトも最終である必要がありますが、これは、「必要なメモリ/フレーム」を減らす/混乱を減らすためだけなので、収集とは関係ありません...
rogerdpack 2017年

11

さて、この場合の「final」修飾子の使用、またはそれがGCに与える影響についてはわかりません。

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

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


1
あなたが正しいです。質問を説明するための簡単な例だけが必要でした。
Goran Martinic

5

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

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

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

final boolean debug = false;

......

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

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


@Eugeneセキュリティマネージャと、コンパイラが変数をインライン化したかどうかによって異なります。
するThorbjörnRavnアンデルセン

そうです、私はただ衒学者でした。これ以上何もない; また、答え
ユージーン

4

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

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

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

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

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

記事:

ガベージコレクションに関するIBM:履歴ホットスポットJVMおよびパフォーマンス。これらは2003/04にさかのぼるため、完全には有効ではなくなっている可能性がありますが、GCに関する読みやすい洞察が得られます。

ガベージコレクションのチューニングに関するSun


3

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


3

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


2

彷徨う推測である答えがたくさんあるようです。真実は、バイトコードレベルでローカル変数の最終的な修飾子がないということです。 仮想マシンは、ローカル変数がfinalとして定義されているかどうかを知ることはありません。

あなたの質問への答えは、はっきりとしたノーです。


それは本当かもしれませんが、コンパイラーはデータフロー分析中に最終情報を使用できます。

@WernerVanBelleコンパイラは、変数が1回だけ設定されることすでに認識しています。変数がnullである可能性があること、使用前に初期化されていないことなどを知るために、データフロー分析をすでに実行する必要があります。したがって、ローカルファイナルはコンパイラに新しい情報を提供しません。
Matt Quigley

そうではありません。データフロー分析は多くのことを推測できますが、ローカル変数を設定する、または設定しないブロック内にチューリング完全なプログラムを含めることは可能です。コンパイラーは、変数が書き込まれるかどうかを事前に知ることができず、定数になることはありません。したがって、コンパイラは、finalキーワードがないと、変数がfinalであるかどうかを保証できません。

@WernerVanBelle私は本当に興味をそそられます、例を挙げていただけますか?コンパイラが認識していない非最終変数への最終代入がどのように行われるのかわかりません。コンパイラは、初期化されていない変数がある場合、それを使用できないことを認識しています。ループ内で最終変数を割り当てようとすると、コンパイラーはそれを許可しません。最終として宣言できるがそうではない変数があり、コンパイラがそれが最終であることを保証できない例は何ですか?どんな例でも、そもそもfinalとして宣言できなかった変数になるのではないかと思います。
Matt Quigley 2014

コメントを読み直したところ、あなたの例は条件付きブロック内で初期化された変数であることがわかりました。すべての条件パスが変数を一度初期化しない限り、これらの変数はそもそも最終的なものにはなりません。したがって、コンパイラはそのような宣言について知っています-これにより、2つの異なる場所で初期化された最終変数をコンパイルできます(think final int x; if (cond) x=1; else x=2;)。したがって、コンパイラは、finalキーワードがない場合、変数がfinalであるかどうかを保証できます。
Matt Quigley 2014

1

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


1

インスタンスフィールドについて厳密に言えば、特定のGCがそれを利用したい場合は、パフォーマンスがわずかに向上するfinal 可能性があります。同時GC発生が発生した場合(つまり、GCの進行中にアプリケーションがまだ実行されている場合)、より広範な説明についてこれを参照してください。GCは、書き込みや読み取りが行われるときに特定のバリアを使用する必要があります。私があなたに与えたリンクはそれをかなり説明していますが、それを本当に短くするために:GCがいくつかの同時作業を行うとき、(そのGCが進行している間)ヒープへのすべての読み取りと書き込みは「傍受」され、後で適用されます。並行GCフェーズがその作業を終了できるようにします。

たとえばfinal、フィールドは(反射がない限り)変更できないため、これらのバリアは省略できます。そして、これは純粋な理論だけではありません。

Shenandoah GCそれらを実際に持っています(長くはありませんが)、そしてあなたは例えばすることができます:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

また、GCアルゴリズムには、わずかに高速化する最適化があります。これはfinal、誰もそれらを変更してはならないため、傍受する障壁がないためです。リフレクションやJNI経由でもありません。


0

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


0

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


0

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

  • 私が持っている彼らに、彼らはいくつかの匿名クラスと共有することができ、最終的なようにするために(例えば:デーモンスレッドを作成し、それがメソッドを囲むからいくつかの値にアクセスしてみましょう)

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

それらは高速ガベージコレクションに役立ちますか?
オブジェクトへの強い参照がゼロの場合、オブジェクトはGCコレクションの候補になります。その場合も、オブジェクトがすぐにガベージコレクションされる保証はありません。一般に、強力な参照は、スコープ外になるか、ユーザーが明示的にnull参照に再割り当てすると消滅すると言われます。したがって、それらを最終として宣言すると、メソッドが存在するまで参照が存在し続けることを意味します(スコープが明示的にに絞り込まれていない限り)最終変数を再割り当てできない(つまり、nullに再割り当てできない)ため、特定の内部ブロック{})。したがって、wrt Garbage Collection'final '、望ましくない遅延を引き起こす可能性があると思います。そのため、GCの候補になるタイミングを制御するスコープを定義する場合は、少し注意する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.