私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Java のオブジェクトはヒープ メモリに割り当てられますか?
抽象的すぎるので、次のオブジェクトのメモリがどこに割り当てられているかを見てみましょう。
- public void test() {
- Object object = new Object();
- }
- 这个方法中的object对象,是在堆中分配内存么?
結果を言うと、オブジェクトはスタックまたはヒープにメモリを割り当てる可能性があります。
ここが重要なポイントです。JVM の実装において、JVM のパフォーマンスを向上させ、メモリ領域を節約するために、JVM は「エスケープ分析」と呼ばれる機能を提供します。エスケープ分析は、現在比較的最先端の最適化テクノロジです。 Java 仮想マシンは、JIT の非常に重要な最適化手法でもあります。 jdk6 はこのテクノロジーの導入を開始したばかりで、jdk7 はデフォルトでエスケープ分析を有効にし始め、jdk8 はエスケープ分析を改善し始め、デフォルトで有効にしました。JDK 9 までは、エスケープ分析はデフォルトの最適化方法として使用され、特別なコンパイル パラメーターはありません。が必要です。
ここで、「オブジェクトはスタックにメモリを割り当てることも、ヒープにメモリを割り当てることもできる」という文を理解してください。jdk7 より前では、ここでのオブジェクトは jdk7 と 8 ではスタックにメモリを割り当てることができます。エスケープ分析のみがサポートされ始めています。jdk9 はデフォルトでエスケープ分析のみを実際にサポートし、有効にするため、スタックに割り当てられる可能性が高くなります (ここでのオブジェクトは非常に小さいです)。
JIT コンパイラ (ジャストインタイム コンパイラ) の開発とエスケープ解析技術の段階的な成熟により、スタック割り当てとスカラー置換の最適化技術により、Java では「すべてのオブジェクトがヒープに割り当てられる」ということが絶対的ではなくなります。仮想マシンでは、オブジェクトはヒープ内にメモリが割り当てられますが、特殊な場合があります。つまり、エスケープ分析後にオブジェクトがメソッドからエスケープしないことが判明した場合、そのオブジェクトはスタック上に割り当てられるように最適化されることがあります。メソッドが実行されるとき 完了すると、スタック フレームがポップされ、オブジェクトが解放されます。これにより、ヒープにメモリを割り当てたり、ガベージ コレクションを実行したりする必要がなくなります。ホットスポットでは現在これを行っていません)。これは最も一般的なオフヒープ ストレージ テクノロジでもあります。
JDK 6u23 (覚えやすいメジャー バージョン JDK7) 以降、ホットスポットではエスケープ分析がデフォルトで有効になっています。それより前のバージョンを使用している場合は、オプション「-XX:+DoEscapeAnalysis」を使用してエスケープ分析を表示できます。エスケープ分析のフィルター結果を表示します。
Hotspot は、エスケープ分析を通じてスカラー置換を実装します (エスケープされていないオブジェクトはスカラーと集計に置き換えられ、コードの効率が向上します)。ただし、エスケープされていないオブジェクトは引き続きヒープ上にメモリを割り当てるため、すべてのオブジェクトが割り当てられていると言えます。ヒープ上のメモリ。
さらに、Open JDKをベースにした高度なカスタマイズタオバオVMそのうち革新的なGCIH(GCインビジブルヒープ)テクノロジーはオフヒープを実装し、ライフサイクルの長いオブジェクトをヒープからヒープ外に移動し、GCはGCIH内のJavaオブジェクトを管理しないため、GCのリサイクル頻度が減り、パフォーマンスが向上します。 GC 回収効率の向上。
スタック:各メソッドが実行されると、ローカル変数テーブル、操作スタック、動的接続、メソッド出口などの情報を保存するスタック フレームが同時に作成されます。各メソッドが呼び出されてから実行が完了するまでの過程は、仮想マシンスタックにおいてスタックフレームがスタックにプッシュされてからスタックからポップアウトされるまでの過程に相当します。
ヒープ:オブジェクトがインスタンス化されると、オブジェクトはヒープに割り当てられ、ヒープへの参照がスタックにプッシュされます。
逃げる:オブジェクトへのポインターが複数のメソッドまたはスレッドによって参照される場合、一般に、ポインターがエスケープし、戻りオブジェクトとグローバル変数がエスケープされるといいます。
エスケープ分析:この脱出現象を解析する手法を脱出解析といいます。
エスケープ分析の最適化 - スタック上の割り当て:スタック上での割り当てとは、メソッド内のローカル変数によって生成されたインスタンス (エスケープは発生しません) がスタック上に割り当てられ、割り当てが完了した後は、実行がコール スタック内で継続されることを意味します。最後に、スレッドが終了し、スタック領域がリサイクルされ、ローカルの Variable オブジェクトもリサイクルされます。
回答: 必ずしもそうとは限りません。
エスケープ解析の条件を満たしていれば、スタック上にオブジェクトを配置することができます。ヒープ メモリの割り当てと GC プレッシャーを軽減します。スタックメモリには限りがあるため、オブジェクトがスカラー置換の条件を満たしていれば、対象に対してさらなる操作が実行され、対象が部分に分割されます。スカラー置換の具体的な方法は次のとおりです。JVM はオブジェクトをさらに分割し、このメソッドで使用される複数のメンバー変数にオブジェクトを分解します。したがって、スタック メモリとレジスタをより有効に利用するという目標が達成されます。
これは、Java プログラムにおける同期負荷とメモリ ヒープ割り当ての圧力を効果的に軽減できる、機能横断的なグローバル データ フロー分析アルゴリズムです。 Java ホットスポット コンパイラは、エスケープ分析を通じて、新しいオブジェクトの参照の使用範囲を分析し、オブジェクトをヒープに割り当てるかどうかを決定できます。
エスケープ分析の基本的な動作は、オブジェクトの動的スコープを分析することです。
コンピュータ言語のコンパイラ最適化原理において、エスケープ解析とは、ポインタのダイナミックレンジを解析する方法を指し、コンパイラ最適化原理のポインタ解析および形状解析に関連する。変数 (またはオブジェクト) がメソッドに割り当てられると、そのポインターがグローバルに返されたり参照されたりすることがあります。この現象は、ポインター (または参照) エスケープと呼ばれます。平たく言えば、オブジェクトのポインターが複数のメソッドまたはスレッドによって参照されている場合、そのオブジェクトのポインター (またはオブジェクト) を Escape と呼びます (このとき、オブジェクトはメソッドまたはスレッドのローカル スコープからエスケープするため)。
簡単な説明: 「エスケープ分析: ポインターのダイナミック レンジを決定する静的分析。プログラム内のどこでポインターにアクセスできるかを分析できます。JVM のジャストインタイム コンパイルのコンテキストでは、エスケープ分析は、新しく作成されたオブジェクトがエスケープします。
オブジェクトがエスケープするかどうかを判断するためのジャストインタイム コンパイルの基準。1 つはオブジェクトがヒープ (静的フィールドまたはヒープ内のオブジェクトのインスタンス フィールド) に格納されているかどうか、もう 1 つはオブジェクトがヒープに渡されるかどうかです。未知のコード。
エスケープ分析は現在、Java 仮想マシンにおける比較的最先端の最適化技術であり、型継承関係分析と同様に、コードを直接最適化する手段ではありませんが、他の最適化手段の基礎を提供する分析技術です。
エスケープ分析: これは非常に重要な JIT 最適化テクノロジであり、オブジェクトがメソッドの外側でアクセスされるかどうか、つまりメソッドのスコープをエスケープするかどうかを判断するために使用されます。エスケープ分析は、JIT コンパイラーのステップです。JIT を通じて、メソッド内での使用を制限でき、外部にはエスケープされないオブジェクトを決定し、それらのオブジェクトをヒープではなくスタックに割り当てるなどの最適化を行うことができます。または、スカラー置換を実行して、オブジェクトを複数の基本タイプに分割して保存します。これは、Java プログラムにおける同期負荷、メモリ ヒープ割り当て、ガベージ コレクションの負荷を効果的に軽減できる、機能横断的なグローバル データ フロー分析アルゴリズムです。 Java ホットスポット コンパイラは、エスケープ分析を通じて、新しいオブジェクトの参照の使用範囲を分析し、このオブジェクトをヒープに割り当てるかどうかを決定できます。
エスケープ分析は主にローカル変数に焦点を当て、ヒープに割り当てられたオブジェクトがメソッドのスコープからエスケープしたかどうかを判断します。これは、コンパイラーの最適化原理のポインター分析と形状分析に関連付けられています。変数 (またはオブジェクト) がメソッドに割り当てられると、そのポインターがグローバルに返されたり参照されたりすることがあります。この現象は、ポインター (または参照) エスケープと呼ばれます。平たく言えば、オブジェクトのポインタが複数のメソッドまたはスレッドによって参照されている場合、オブジェクトのポインタがエスケープしたと言います。コード構造とデータ使用法を適切に設計すると、エスケープ分析をより効果的に利用してプログラムのパフォーマンスを最適化できます。また、ヒープ上にオブジェクトを割り当てるオーバーヘッドを削減し、エスケープ分析を通じてメモリ使用率を向上させることもできます。
エスケープ分析は、オブジェクトが存続期間中にメソッドの範囲外にエスケープしたかどうかを判断するために使用される手法です。 Java 開発では、エスケープ解析を使用してオブジェクトのライフサイクルとスコープを決定し、対応する最適化を実行し、プログラムのパフォーマンスとメモリ使用効率を向上させます。
オブジェクトが作成されると、そのオブジェクトはメソッド内で使用することも、他のメソッドまたはスレッドに渡してメソッドの外部に存在し続けることもできます。オブジェクトがメソッドのスコープからエスケープしない場合、JVM はオブジェクトをヒープではなくスタックに割り当てることができるため、ヒープ メモリの割り当てとガベージ コレクションのオーバーヘッドが回避されます。
- 关于逃逸分析的论文在1999年就已经发表,但直到Sun JDK 1.6才实现了逃逸分析,而且直到现在这项优化尚未足够成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸分析的性能收益必定高于它的消耗。如果要完全准确的判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。这是一个相对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分析。还有一点是,基于逃逸分析的一些优化手段,如上面提到的“栈上分配”,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项优化。
- 在测试结果中,实施逃逸分析后的程序在MicroBenchmarks中往往能运行出不错的成绩,但是在实际的应用程序,尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定的情况,或因分析过程耗时但却无法有效判别出非逃逸对象而导致性能(即使编译的收益)有所下降,所以在很长的一段时间里,即使是Server Compiler,也默认不开启逃逸分析(在JDK 1.6 Update 23的Server Compiler中才开始默认开启了逃逸分析),甚至在某些版本(如JDK 1.6 Update 18)中还曾经短暂的完全禁止了这项优化。
- 如果有需要,并且确认对程序运行有益,用户可以使用参数-XX:+DoEscapeAnalysis来手动开启逃逸分析,开启之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。有了逃逸分析支持之后,用户可以使用参数-XX:EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:PrintEliminateAllocations查看标量的替换情况。
- 尽管目前逃逸分析的技术仍不是十分成熟,但是他却是即时编译器优化技术的一个重要的方向,在今后的虚拟机中,逃逸分析技术肯定会支撑起一系列使用有效的优化技术。
JVM エスケープ分析の基本原理は、静的および動的という 2 つの分析方法を通じてオブジェクトのエスケープ状況を判断することです。
Java コンパイル システムでは、Java ソース コード ファイルをコンピュータで実行可能な機械命令に変換するプロセスには、次の 2 段階のコンパイルが必要です。
コンパイルの最初のセクションはフロントエンド コンパイラーを参照します。.java ファイルに変換.class ファイル (バイトコードファイル)。フロントエンド コンパイラ製品には、JDK の Javac または Eclipse JDT のインクリメンタル コンパイラを使用できます。
2 番目のコンパイル段階では、JVM はバイトコードを解釈して対応するマシン命令に変換し、バイトコードを 1 つずつ読み取り、それを 1 つずつ解釈してマシンコードに変換します。
明らかに、解釈の中間プロセスにより、その実行速度は、実行可能なバイナリ バイトコード プログラムよりも必然的に大幅に遅くなります。これは従来の JVM インタプリタ (Interpreter) の機能です。
この効率の問題を解決するために、JIT (Just In Time Compiler) テクノロジーが導入されました。
JIT テクノロジの導入後も、Java プログラムはインタプリタを介して解釈および実行されます。つまり、本体は依然として解釈および実行されますが、中間リンクは部分的に削除されます。
JIT コンパイラ (Just-in-timeCompiler) ジャストインタイム コンパイル。最も初期の Java 実装ソリューションは、各 Java 命令を同等のマイクロプロセッサ命令に変換し、変換された命令の順序に従って順番に実行する一連のトランスレータ (インタプリタ) で構成されていました。これは、Java 命令を数十個の命令に変換できるためです。同等のマイクロプロセッサ命令と同様に、このモードの実行は非常に遅くなります。
JVM は、特定のメソッドまたはコード ブロックが特に頻繁に実行されていることを検出すると、それを「ホット スポット コード」と見なします。次に、JIT は「ホット コード」の一部をローカル マシンに関連するマシン コードに変換して最適化し、次に使用するために変換されたマシン コードをキャッシュします。
変換されたマシンコードをどこにキャッシュするか? このキャッシュはコード キャッシュと呼ばれます。 JVM アプリケーションと WEB アプリケーションの間で高い同時実行性を実現する方法は類似しており、依然としてキャッシュ アーキテクチャを使用していることがわかります。
JVM は次回同じホット コードに遭遇すると、解釈の中間リンクをスキップし、コード キャッシュからマシン コードを直接ロードし、再度コンパイルせずに直接実行します。
したがって、JIT の全体的な目標はホット コードを発見することであり、ホット コードがパフォーマンス向上の鍵となるのは、ホット コードを特定してその名前に記述するという名前が付けられた理由です。
したがって、JVM の全体的な戦略は次のとおりです。
珍しいコードのほとんどについては、それらをマシンコードにコンパイルするのに時間を費やす必要はなく、解釈と実行を通じてコードを実行します。
一方、ほんの一部しか占めないホット コードについては、それをマシン コードにコンパイルして、理想的な実行速度を実現できます。
JIT(ジャスト・イン・タイム・コンパイル)の登場とインタプリタの違い
(1) インタプリタはバイトコードを機械語に解釈します。次に同じバイトコードに遭遇した場合でも、解釈を繰り返します。
(2) JIT は一部のバイトコードをマシンコードにコンパイルし、コードキャッシュに保存します。次回同じコードに遭遇した場合、再度コンパイルせずに直接実行されます。
(3) インタプリタはバイトコードをすべてのプラットフォームに共通のマシンコードに解釈します。
(4) JIT は、プラットフォームの種類に基づいてプラットフォーム固有のマシン コードを生成します。
JVM には、複数のジャストインタイム コンパイラ (主に C1 と C2、および Graal (実験版)) が含まれています。
複数のジャストインタイム コンパイラがバイトコードを最適化し、マシンコードを生成します。
JVM は実行ステータスを 5 つのレベルに分けます。
レベル0、通訳
レベル 1、C1 ジャストインタイム コンパイラを使用してコンパイルおよび実行 (プロファイリングなし)
レイヤ 2、C1 ジャストインタイム コンパイラを使用してコンパイルおよび実行 (基本プロファイリング付き)
レイヤ 3、C1 ジャストインタイム コンパイラを使用してコンパイルおよび実行 (完全なプロファイリング付き)
レベル 4、C2 ジャストインタイム コンパイラを使用してコンパイルおよび実行
JVM は C2 を直接有効にするのではなく、まず C1 のコンパイルを通じてプログラムの実行ステータスを収集し、その分析結果に基づいて C2 を有効にするかどうかを決定します。
階層化コンパイル モードでは、仮想マシンの実行ステータスが、単純から複雑、高速から低速まで 5 つの階層に分割されます。
コンパイル中、プロセスを高速化するためにホット コードをキャッシュすることに加えて、JIT はコードに対して多くの最適化も実行します。
一部の最適化の目的は次のとおりです。メモリヒープ割り当てのプレッシャーを軽減する 、JIT 最適化における重要な手法の 1 つはエスケープ分析と呼ばれます。エスケープ分析によると、ジャストインタイム コンパイラーはコンパイル プロセス中に次のようにコードを最適化します。
コードの静的構造をチェックして、オブジェクトがエスケープできるかどうかを判断します。例えば、オブジェクトがクラスのメンバ変数に代入されたり、外部メソッドに返されたりした場合、オブジェクトがエスケープしていると判断できる。
メソッド呼び出しとオブジェクト参照の動作を観察することで、オブジェクトがエスケープするかどうかを判断します。たとえば、オブジェクトが複数のスレッドから参照されている場合、そのオブジェクトはエスケープしたと判断できます。
エスケープ分析では、コードの詳細な分析を実行して、メソッドの有効期間中にオブジェクトがメソッドのスコープ外にエスケープしたかどうかを判断します。オブジェクトがエスケープしない場合、JVM はオブジェクトをヒープではなくスタックに割り当てることができます。
オブジェクトには、グローバル エスケープ、パラメータ エスケープ、およびエスケープなしの 3 つのエスケープ状態があります。
グローバルエスケープ(GlobalEscape): つまり、オブジェクトのスコープが現在のメソッドまたは現在のスレッドからエスケープします。
一般に、次のようなシナリオがあります。
① オブジェクトは静的変数です
② 物体が逃げ出した物体である
③ オブジェクトは現在のメソッドの戻り値として使用されます
パラメータエスケープ(ArgEscape): つまり、オブジェクトはメソッド パラメーターとして渡されるか、パラメーターによって参照されますが、呼び出しプロセス中にグローバル エスケープは発生しません。この状態は、呼び出されたメソッドのバイトコードによって決まります。
逃げ場はありません: つまり、メソッド内のオブジェクトはエスケープされません。
エスケープ状態のサンプル コードは次のとおりです。
- public class EscapeAnalysisTest {
-
- public static Object globalVariableObject;
-
- public Object instanceObject;
-
- public void globalVariableEscape(){
- globalVariableObject = new Object(); // 静态变量,外部线程可见,发生逃逸
- }
-
- public void instanceObjectEscape(){
- instanceObject = new Object(); // 赋值给堆中实例字段,外部线程可见,发生逃逸
- }
-
- public Object returnObjectEscape(){
- return new Object(); // 返回实例,外部线程可见,发生逃逸
- }
-
- public void noEscape(){
- Object noEscape = new Object(); // 仅创建线程可见,对象无逃逸
- }
-
- }
1. メソッド エスケープ: メソッド本体で、呼び出しパラメーターとしてメソッドに渡される、またはオブジェクトとして直接返されるなど、外部メソッドによって参照されるローカル変数を定義します。あるいは、メソッドからオブジェクトが飛び出しているとも理解できます。
メソッドのエスケープには次のものが含まれます。
- 我们可以用下面的代码来表示这个现象。
-
- //StringBuffer对象发生了方法逃逸
- public static StringBuffer createStringBuffer(String s1, String s2) {
- StringBuffer sb = new StringBuffer();
- sb.append(s1);
- sb.append(s2);
- return sb;
- }
- 上面的例子中,StringBuffer 对象通过return语句返回。
-
- StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。
-
- 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
-
- 不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。
-
- 具体的代码如下:
-
- // 非方法逃逸
- public static String createString(String s1, String s2) {
- StringBuffer sb = new StringBuffer();
- sb.append(s1);
- sb.append(s2);
- return sb.toString();
- }
- 可以看出,想要逃逸方法的话,需要让对象本身被外部调用,或者说, 对象的指针,传递到了 方法之外。
エスケープ分析が発生したかどうかをすばやく判断するにはどうすればよいでしょうか。新しいオブジェクト エンティティがメソッドの外で呼び出されているかどうかを見てみましょう。
- public class EscapeAnalysis {
-
- public EscapeAnalysis obj;
-
- /**
- * 方法返回EscapeAnalysis对象,发生逃逸
- * @return
- */
- public EscapeAnalysis getInstance() {
- return obj == null ? new EscapeAnalysis():obj;
- }
-
- /**
- * 为成员属性赋值,发生逃逸
- */
- public void setObj() {
- this.obj = new EscapeAnalysis();
- }
-
- /**
- * 对象的作用于仅在当前方法中有效,没有发生逃逸
- */
- public void useEscapeAnalysis() {
- EscapeAnalysis e = new EscapeAnalysis();
- }
-
- /**
- * 引用成员变量的值,发生逃逸
- */
- public void useEscapeAnalysis2() {
- EscapeAnalysis e = getInstance();
- }
- }
2. スレッド エスケープ: このオブジェクトは、インスタンス変数に割り当てられたり、他のスレッドによってアクセスされたりするなど、他のスレッドによってアクセスされます。オブジェクトは現在のスレッドからエスケープされました。
エスケープ分析により、Java プログラムに次の最適化戦略を導入できます。スタックへの割り当て、同期の削除、スカラー置換、メソッドのインライン化です。
エスケープ分析関連パラメータ:
- -XX:+DoEscapeAnalysis 开启逃逸分析
- -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
- -XX:+EliminateAllocations 开启标量替换
- -XX:+EliminateLocks 开启同步消除
- -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。
エスケープ分析では、どのオブジェクトがメソッドのスコープからエスケープしないかを判断し、これらのオブジェクトをヒープではなくスタックに割り当てることができます。スタック上に割り当てられたオブジェクトは、ガベージ コレクションなしでメソッド呼び出しライフ サイクル内で作成および破棄されるため、プログラムの実行効率が向上します。
通常の状況では、エスケープできないオブジェクトは比較的大きなスペースを占有します。スタック上のスペースが使用できる場合、メソッドの終了時に多数のオブジェクトが破棄され、GC の圧力が軽減されます。
スタック上の割り当てのアイデアスタック上の割り当ては、JVM によって提供される最適化テクノロジです。
アイデアは次のとおりです。
問題: スタック メモリが比較的小さいため、大きなオブジェクトはスタックに割り当てることができず、スタックに割り当てるのは適していません。
オンスタック割り当てを有効にする
スタック上の割り当てはエスケープ解析とスカラー置換に基づいているため、エスケープ解析とスカラー置換を有効にする必要があります。当然、JDK1.8 はデフォルトで有効になっています。
- 开启逃逸分析:-XX:+DoEscapeAnalysis
- 关闭逃逸分析:-XX:-DoEscapeAnalysis
- 显示分析结果:-XX:+PrintEscapeAnalysis
-
-
- 开启标量替换:-XX:+EliminateAllocations
- 关闭标量替换:-XX:-EliminateAllocations
- 显示标量替换详情:-XX:+PrintEliminateAllocations
スタック上の割り当ての例:
- 示例1
- import java.lang.management.ManagementFactory;
- import java.util.List;
- /**
- * 逃逸分析优化-栈上分配
- * 栈上分配,意思是方法内局部变量(未发生逃逸)生成的实例在栈上分配,不用在堆中分配,分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。
- * 一般生成的实例都是放在堆中的,然后把实例的指针或引用压入栈中。
- *虚拟机参数设置如下,表示做了逃逸分析 消耗时间在10毫秒以下
- * -server -Xmx10M -Xms10M
- -XX:+DoEscapeAnalysis -XX:+PrintGC
- *
- *虚拟机参数设置如下,表示没有做逃逸分析 消耗时间在1000毫秒以上
- * -server -Xmx10m -Xms10m
- -XX: -DoEscapeAnalysis -XX:+PrintGC
- * @author 734621
- *
- */
-
- public class OnStack{
- public static void alloc(){
- byte[] b=new byte[2];
- b[0]=1;
- }
- public static void main(String [] args){
- long b=System.currentTimeMillis();
- for(int i=0;i<100000000;i++){
- alloc();
- }
- long e=System.currentTimeMillis();
- System.out.println("消耗时间为:" + (e - b));
- List<String> paramters = ManagementFactory.getRuntimeMXBean().getInputArguments();
- for(String p : paramters){
- System.out.println(p);
- }
- }
- }
-
-
- 加逃逸分析的结果
- [GC (Allocation Failure) 2816K->484K(9984K), 0.0013117 secs]
- 消耗时间为:7
- -Xmx10m
- -Xms10m
- -XX:+DoEscapeAnalysis
- -XX:+PrintGC
-
-
-
- 没有加逃逸分析的结果如下:
- [GC (Allocation Failure) 3320K->504K(9984K), 0.0003174 secs]
- [GC (Allocation Failure) 3320K->504K(9984K), 0.0002524 secs]
- 消耗时间为:1150
- -Xmx10m
- -Xms10m
- -XX:-DoEscapeAnalysis
- -XX:+PrintGC
-
-
- 以上测试可以看出,栈上分配可以明显提高效率: 效率是不开启的1150/7= 160倍
-
-
- 示例2
- 我们通过举例来说明 开启逃逸分析 和 未开启逃逸分析时候的情况
-
- class User {
- private String name;
- private String age;
- private String gender;
- private String phone;
- }
- public class StackAllocation {
- public static void main(String[] args) throws InterruptedException {
- long start = System.currentTimeMillis();
- for (int i = 0; i < 100000000; i++) {
- alloc();
- }
- long end = System.currentTimeMillis();
- System.out.println("花费的时间为:" + (end - start) + " ms");
-
- // 为了方便查看堆内存中对象个数,线程sleep
- Thread.sleep(10000000);
- }
-
- private static void alloc() {
- // 未发生逃逸
- User user = new User();
- }
- }
- 设置JVM参数,表示未开启逃逸分析
- -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
- 花费的时间为:664 ms
- 然后查看内存的情况,发现有大量的User存储在堆中
-
- 开启逃逸分析
- -Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
- 然后查看运行时间,我们能够发现花费的时间快速减少,同时不会发生GC操作
- 花费的时间为:5 ms
- 在看内存情况,我们发现只有很少的User对象,说明User未发生逃逸,因为它存储在栈中,随着栈的销毁而消失。
比較すると、次のことがわかります。
エスケープ分析では、特定のオブジェクトが単一のスレッドによってのみアクセスされ、他のスレッドにエスケープされないことを検出できます。したがって、不要な同期操作が排除され、マルチスレッド プログラムの実行オーバーヘッドが軽減されます。
同期ロックはパフォーマンスを非常に消費するため、コンパイラはオブジェクトがエスケープしていないと判断すると、オブジェクトから同期ロックを削除します。 JDK1.8 はデフォルトで同期ロックを有効にしますが、これはエスケープ分析の有効化に基づいています。
- -XX:+EliminateLocks #开启同步锁消除(JVM默认状态)
- -XX:-EliminateLocks #关闭同步锁消除
- 通过示例: 明显可以看到“逃逸分析和锁消除” 对性能的提升
-
- public void testLock(){
- long t1 = System.currentTimeMillis();
- for (int i = 0; i < 100_000_000; i++) {
- locketMethod();
- }
- long t2 = System.currentTimeMillis();
- System.out.println("耗时:"+(t2-t1));
- }
-
- public static void locketMethod(){
- EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
- synchronized(escapeAnalysis) {
- escapeAnalysis.obj2="abcdefg";
- }
- }
-
- 设置JVM参数,开启逃逸分析, 耗时:
- java -Xmx64m -Xms64m -XX:+DoEscapeAnalysis
-
- 设置JVM参数,关闭逃逸分析, 耗时:
- java -Xmx64m -Xms64m -XX:-DoEscapeAnalysis
-
- 设置JVM参数,关闭锁消除,再次运行
- java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateLocks
-
- 设置JVM参数,开启锁消除,再次运行
- java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateLocks
-
-
スレッド同期のコストは非常に高く、同期の結果として同時実行性とパフォーマンスが低下します。
同期ブロックを動的にコンパイルする場合、JIT コンパイラーはエスケープ分析を使用して、同期ブロックが使用するロック オブジェクトが 1 つのスレッドによってのみアクセス可能であり、他のスレッドに解放されていないかを判断できます。そうでない場合、JIT コンパイラーは、この同期ブロックをコンパイルするときに、コードのこの部分を非同期化します。これにより、同時実行性とパフォーマンスが大幅に向上します。この同期を取り消すプロセスは同期省略と呼ばれ、ロックの削除とも呼ばれます。
- 例如下面的代码
-
- public void f() {
- Object hellis = new Object();
- synchronized(hellis) {
- System.out.println(hellis);
- }
- }
- 代码中对hellis这个对象加锁,但是hellis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉,优化成:
-
- public void f() {
- Object hellis = new Object();
- System.out.println(hellis);
- }
- 我们将其转换成字节码,此处发现,还是有同步锁的身影,是因为优化是在编译阶段的,在加载进内存后发生。
エスケープ分析では、オブジェクトをプリミティブ型や他のオブジェクトなどの複数のスカラーに分割し、それらを異なる場所に割り当てることができます。これにより、メモリの断片化とオブジェクト アクセスのオーバーヘッドが軽減され、メモリの使用効率が向上します。
まず最初に、スカラーと集合体について理解する必要があります。基本的な型とオブジェクトへの参照はスカラーとして理解でき、それ以上分解することはできません。さらに分解できる量は、オブジェクトなどの集合量です。
オブジェクトは集合量であり、さらにスカラーに分解し、そのメンバー変数を離散変数に分解できます。これはスカラー置換と呼ばれます。
この方法では、オブジェクトがエスケープしない場合、オブジェクトを作成する必要はまったくありません。オブジェクトによって使用されるメンバー スカラーのみがスタックまたはレジスタ上に作成されるため、メモリ領域が節約され、アプリケーションのパフォーマンスが向上します。
JDK1.8 ではスカラー置換もデフォルトで有効になっていますが、有効になっているエスケープ分析に基づく必要もあります。
スカラーは、より小さなデータに分割できないデータです。 Java のプリミティブ データ型はスカラーです。
対照的に、分解可能なデータは集合体と呼ばれます。Java のオブジェクトは他の集合体とスカラーに分解できるため、集合体と呼ばれます。
- public static void main(String args[]) {
- alloc();
- }
- class Point {
- private int x;
- private int y;
- }
- private static void alloc() {
- Point point = new Point(1,2);
- System.out.println("point.x" + point.x + ";point.y" + point.y);
- }
- 以上代码,经过标量替换后,就会变成
-
- private static void alloc() {
- int x = 1;
- int y = 2;
- System.out.println("point.x = " + x + "; point.y=" + y);
- }
JIT 段階では、エスケープ解析によりオブジェクトが外部からアクセスされないことが判明した場合、JIT 最適化後、オブジェクトはそれに含まれる複数のメンバー変数に分解され、置き換えられます。この処理がスカラー置換です。
エスケープ分析の後、集合量 Point がエスケープされなかったことが判明したため、2 つのスカラーに置き換えられたことがわかります。では、スカラー置換にはどのような利点があるのでしょうか?つまり、ヒープ メモリの使用量を大幅に削減できます。オブジェクトを作成する必要がなくなると、ヒープ メモリを割り当てる必要もなくなるためです。 スカラー置換は、スタック上での割り当ての適切な基礎を提供します。
脱出分析テスト
- 逃逸分析测试
- 代码如下,大致思路就是 for 循环 1 亿次,循环体内调用外部的 allot() 方法,而 allot() 方法的作用就是简单创建一个对象,但是这个对象是内部的,所以是未逃逸的,所以理论上 JVM 是会进行优化的,我们拭目以待。并且我们会对比开启和关闭逃逸分析之后各自程序的运行时间:
-
- /**
- * @ClassName: EscapeAnalysisTest
- * @Description: http://www.jetchen.cn 逃逸分析 demo
- * @Author: Jet.Chen
- * @Date: 2020/11/23 14:26
- * @Version: 1.0
- **/
- public class EscapeAnalysisTest {
-
- public static void main(String[] args) {
- long t1 = System.currentTimeMillis();
- for (int i = 0; i < 100000000; i++) {
- allot();
- }
- long t2 = System.currentTimeMillis();
- System.out.println(t2-t1);
- }
-
- private static void allot() {
- Jet jet = new Jet();
- }
-
- static class Jet {
- public String name;
- }
-
- }
- 上面就是我们进行逃逸分析测试的代码, mian() 方法末尾有一个线程暂停,目的是为了观察此时 JVM 中的内存情况。
-
- Step 1:测试开启逃逸
- 由于环境是 jdk1.8,默认开启了逃逸分析,所以直接运行,得到结果如下,程序耗时 3 毫秒:
-
-
- 此时线程是处于睡眠状态的,我们观察下内存情况,发现堆内存中一共新建了 11 万个 Jet 对象。
-
-
-
- Step 2:测试关闭逃逸
- 我们关闭逃逸分析再来运行一次(使用 java -XX:-DoEscapeAnalysis EscapeAnalysisTest 来运行代码即可),得到结果如下,程序耗时 400 毫秒:
-
-
- 此时我们观察下内存情况,发现堆内存中一共新建了 3 千多万个 Jet 对象。
-
-
- 所以,无论是从代码的执行时间(3 毫秒 VS 400 毫秒),还是从堆内存中对象的数量(11 万个 VS 3 千万个)来分析,在上述场景下,开启逃逸分析是有正向益的。
-
- Step 3:测试标量替换
- 我们测试下开启和关闭 标量替换,如下图:
-
-
- 由上图我们可以看出,在上述极端场景下,开启和关闭标量替换对于性能的影响也是满巨大的,另外,同时也验证了标量替换功能生效的前提是逃逸分析已经开启,否则没有意义。
-
- Step 4:测试锁消除
- 测试锁消除,我们需要简单调整下代码,即给 allot() 方法中的内容加锁处理,如下:
-
- private static void allot() {
- Jet jet = new Jet();
- synchronized (jet) {
- jet.name = "jet Chen";
- }
- }
- 然后我们运行测试代码,测试结果也很明显,在上述场景下,开启和关闭锁消除对程序性能的影响也是巨大的。
-
-
- /**
- * 进行两种测试
- * 关闭逃逸分析,同时调大堆空间,避免堆内GC的发生,如果有GC信息将会被打印出来
- * VM运行参数:-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
- *
- * 开启逃逸分析 jdk8默认开启
- * VM运行参数:-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
- *
- * 执行main方法后
- * jps 查看进程
- * jmap -histo 进程ID
- *
- */
- @Slf4j
- public class EscapeTest {
-
- public static void main(String[] args) {
- long start = System.currentTimeMillis();
- for (int i = 0; i < 500000; i++) {
- alloc();
- }
- long end = System.currentTimeMillis();
- log.info("执行时间:" + (end - start) + " ms");
- try {
- Thread.sleep(Integer.MAX_VALUE);
- } catch (InterruptedException e1) {
- e1.printStackTrace();
- }
- }
-
-
- /**
- * JIT编译时会对代码进行逃逸分析
- * 并不是所有对象存放在堆区,有的一部分存在线程栈空间
- * Ponit没有逃逸
- */
- private static String alloc() {
- Point point = new Point();
- return point.toString();
- }
-
- /**
- *同步省略(锁消除) JIT编译阶段优化,JIT经过逃逸分析之后发现无线程安全问题,就会做锁消除
- */
- public void append(String str1, String str2) {
- StringBuffer stringBuffer = new StringBuffer();
- stringBuffer.append(str1).append(str2);
- }
-
- /**
- * 标量替换
- *
- */
- private static void test2() {
- Point point = new Point(1,2);
- System.out.println("point.x="+point.getX()+"; point.y="+point.getY());
-
- // int x=1;
- // int y=2;
- // System.out.println("point.x="+x+"; point.y="+y);
- }
-
-
- }
-
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- class Point{
- private int x;
- private int y;
- }
エスケープ分析により、特定のメソッド呼び出しが現在のメソッドのスコープをエスケープしないことが判断できます。したがって、これらのメソッドをインラインで最適化して、メソッド呼び出しのコストを削減し、プログラムの実行効率を向上させることができます。
これらの最適化戦略を通じて、エスケープ分析は、JVM によるコードの最適化、ガベージ コレクションのオーバーヘッドの削減、プログラムの実行効率と応答性の向上、メモリ使用量の削減に役立ちます。
エスケープ分析には、実際の Java アプリケーションにおける幅広いアプリケーション シナリオがあります。次に、一般的なアプリケーション シナリオをいくつか示します。
エスケープ解析に関する論文は 1999 年に発表されましたが、実装されたのは JDK1.6 であり、この技術はまだあまり成熟していません。
根本的な理由は、エスケープ分析によるパフォーマンス消費がその消費よりも大きくなるという保証がないことです。ただし、エスケープ解析ではスカラー置換、スタック割り当て、ロックの削除を行うことができます。ただし、脱出分析自体も一連の複雑な分析を必要とし、実際には比較的時間のかかるプロセスです。
極端な例では、エスケープ分析の結果、エスケープしないオブジェクトがないことが判明します。そうなると、エスケープ分析のプロセスが無駄になります。
この技術はあまり成熟していませんが、ジャストインタイムのコンパイラ最適化技術においては非常に重要な手段でもあります。エスケープ解析を通じて、JVM がエスケープしないオブジェクトをスタックに割り当てるという意見があることに気づきました。これは理論的には可能ですが、それは JvM 設計者の選択によって異なります。私の知る限り、Oracle Hotspot JVM ではこれが行われません。これはエスケープ分析関連のドキュメントで説明されているため、すべてのオブジェクト インスタンスがヒープ上に作成されることは明らかです。
現在、多くの書籍は依然として JDK7 より前のバージョンに基づいており、JDK は大幅な変更を受けており、かつてはインターン文字列と静的変数のキャッシュが永続世代に割り当てられており、永続世代はメタデータ領域に置き換えられています。ただし、インターン文字列キャッシュと静的変数はメタデータ領域に転送されず、ヒープ上に直接割り当てられるため、これは、オブジェクト インスタンスがヒープ上に割り当てられるという前の点の結論とも一致します。上の例は、スカラー置換により高速化されています。
オブジェクトがメソッド本体内またはスレッド内でエスケープしない場合 (またはエスケープ分析後にエスケープに失敗したと判断された場合)、次の最適化を行うことができます。
通常の状況では、エスケープできないオブジェクトは比較的大きなスペースを占有します。スタック上のスペースが使用できる場合、メソッドの終了時に多数のオブジェクトが破棄され、GC の圧力が軽減されます。
定義したクラスのメソッドに同期ロックがあるにもかかわらず、実行時に 1 つのスレッドだけがそれにアクセスしている場合、エスケープ解析後のマシン コードは同期ロックなしで実行されます。
Java 仮想マシンのプリミティブ データ型 (int、long、参照型などの数値型) はそれ以上分解できず、スカラーと呼ばれることがあります。対照的に、データの一部が分解し続けることができる場合、それは集計と呼ばれます。Java での最も典型的な集計はオブジェクトです。エスケープ分析により、オブジェクトが外部からアクセスされず、そのオブジェクトが分解可能であることが証明された場合、プログラムが実際に実行されるときにオブジェクトは作成されず、代わりにこのメソッドによって使用されるいくつかのメンバー変数が直接作成されます。逆アセンブルされた変数は、個別に分析および最適化できるため、参照ポインターを介して関係を確立する必要がなく、継続的かつコンパクトに保存できるため、さまざまなストレージに使いやすく、処理中のデータ処理が大幅に節約されます。実行するとパフォーマンスが低下します。同時に、スタック フレームまたはレジスタにそれぞれスペースを割り当てることもできるため、元のオブジェクトが全体としてスペースを割り当てる必要がなくなります。
若い世代は、オブジェクトが生成され、成長し、消滅する領域です。オブジェクトはここで生成され、使用され、最終的にはガベージ コレクターによって収集され、その寿命を終えます。
古い世代に配置されるライフサイクルの長いオブジェクトは、通常、Survivor 領域からコピーされた Java オブジェクトです。もちろん、通常のオブジェクトが TLAB に割り当てられることはわかっていますが、オブジェクトが大きすぎる場合、JVM はそれを Eden の他の場所に直接割り当てようとします。新しい世代のスペースで十分な長さの連続した空きスペースを見つけることができた場合、JVM はそれを古い世代に直接割り当てます。 GC が若い世代でのみ発生する場合、若い世代のオブジェクトをリサイクルする行為は、MinorGc と呼ばれます。
古い世代で GC が発生した場合、MajorGc または FullGC と呼ばれます。一般に、MinorGc の発生頻度は MajorGC よりもはるかに高く、古い世代のガベージ コレクションの頻度は若い世代のそれよりもはるかに低くなります。
JVM エスケープ分析では、静的および動的という 2 つの分析メソッドを使用して、オブジェクトがメソッドのスコープをエスケープする可能性があるかどうかを判断します。これは、JVM がコードを最適化し、Java プログラムのパフォーマンスとメモリ使用効率を向上させるのに役立ちます。
エスケープ分析の最適化戦略には、スタック上の割り当て、同期の削除、スカラー置換、およびメソッドのインライン化が含まれます。これらの最適化戦略により、ガベージ コレクションのオーバーヘッドが削減され、プログラムの実行効率と応答性が向上し、メモリ使用量が削減されます。
参照する:
https://zhuanlan.zhihu.com/p/693382698
JIT メモリ エスケープ分析_Java がスカラー置換をオフにする - CSDN ブログ
java -XX:+PrintFlagsFinal #输出打印所有参数jvm参数