ホット スポット JVM の最適化に関する面白い事実です。現在のメソッドの外で渡されないオブジェクトをインスタンス化すると、JVM はバイトコード レベルで最適化を実行します。
通常、スタック割り当ては、C++ などのメモリ モデルを公開する言語に関連付けられています。delete
スコープが終了すると自動的に割り当てが解除されるため、C++ で変数をスタックする必要はありません。これは、使い終わったらポインターを削除する必要があるヒープ割り当てとは対照的です。
Hot Spot JVM では、オブジェクトがスレッドを「エスケープ」できるかどうかを判断するために、バイトコードが分析されます。エスケープには3 つのレベルがあります。
- エスケープなし - オブジェクトは作成されたメソッド/スコープ内でのみ使用され、オブジェクトはスレッド外ではアクセスできません。
- ローカル/引数エスケープ - オブジェクトは、それを作成するメソッドによって返されるか、それが呼び出すメソッドに渡されますが、これらのメソッドのいずれも、そのオブジェクトをスレッド外でアクセスできる場所に配置しません。
- グローバル エスケープ - オブジェクトは、別のスレッドでアクセスできる場所に配置されます。
これは基本的に、1) 別のメソッドに渡すか返すか、および 2) GC ルートにアタッチされたものやフィールドClassLoader
に格納されたものに関連付けるかという質問に似ています。static
特定のケースでは、匿名オブジェクトは「ローカルエスケープ」としてタグ付けされます。これはsynchronized
、オブジェクトのロック (読み取り: の使用) が最適化されて取り除かれることを意味するだけです。(別のスレッドで使用されることのないものを同期するのはなぜですか?) これは、スタック上で割り当てを行う「エスケープなし」とは異なります。この「割り当て」はヒープ割り当てと同じではないことに注意することが重要です。実際に行うことは、非エスケープ オブジェクト内のすべての変数にスタック上のスペースを割り当てることです。no-escape オブジェクト内にint
、String
、の3 つのフィールドがある場合、 、参照、および参照の3つのスタック変数が割り当てられます。MyObject
int
String
MyObject
MyObject
インスタンス自体は、「エスケープなし」と分析されない限り、ヒープに格納されます。次に、オブジェクトの割り当てが最適化され、コンストラクター/メソッドがヒープ変数の代わりにローカル スタック変数を使用して実行されます。
そうは言っても、私には時期尚早の最適化のように思えます。コードが遅く、パフォーマンスの問題を引き起こしていることが後で証明されない限り、可読性を低下させるようなことは何もすべきではありません。私にとって、このコードはかなり読みやすいので、そのままにしておきます。もちろん、これは完全に主観的なものですが、「パフォーマンス」は、実際の実行時間と関係がない限り、コードを変更する正当な理由にはなりません。通常、最適化が時期尚早であると、コードの保守が難しくなり、パフォーマンス上のメリットは最小限に抑えられます。
Java 8+ とラムダ
匿名インスタンスの割り当てがまだ気になる場合は、単一の抽象メソッド (SAM) 型にラムダを使用するように切り替えることをお勧めします。Lambda 評価は を使用invokedynamic
して実行され、実装は最初の呼び出しで Lambda の単一インスタンスのみを作成することになります。詳細については、こちらの回答とこちらの回答をご覧ください。SAM 以外のタイプの場合でも、匿名インスタンスを割り当てる必要があります。ここでのパフォーマンスへの影響は、ほとんどのユースケースで無視できますが、IMO では、この方法の方が読みやすいです。
参考文献