JMMは、Javaを深く理解したいプログラマにとっては避けられないレベルです。この記事はより理論的であり、可能な限り理解しやすいものです。間違いがあれば修正していただければ幸いです。
それでは、まずjvmのメインメモリ割り当てについて話しましょう
1 java仮想マシンスタック(java仮想スタック)
仮想マシンスタックはスレッドに対してプライベートであり、各スレッドは独自の仮想マシン スタック マシン スタックは、Java メソッドの実行に使用されるメモリ モデル であり、各メソッドが実行されると、仮想マシン スタック上にスタック フレームが作成され、主にローカル 変数 が格納されます。メソッド (基本 Type、objectreference、returnAddress タイプ (バイトコード命令のアドレスを指す))、操作スタック (メソッドのコンパイル後の操作命令のスタックを参照)、動的リンク、メソッド終了。一般に、Java メモリはスタックとヒープに分けられ、スタックとは仮想マシンのスタックを指します。しかし、Java のメモリ割り当てはそれほど単純ではありません。
ダイナミック リンクについては、次のように説明されます。
各スタック フレームには、プール内でスタック フレームが属するメソッドへの実行ランタイム 定数 参照が含まれます。この参照は、 ダイナミック リンク (ダイナミック リンク) をサポートするために保持されます。
クラス バイトコード内のメソッド呼び出し命令は、定数プール内のメソッドを指すシンボル参照をパラメータとして受け取ります。これらのシンボル参照の一部は、クラスの読み込みフェーズ中、または初めて使用されるときに直接参照に変換されます。この変換は静的解析と呼ばれます。他の部分は各実行中に直接参照に変換され、この部分は Dynamic Link と呼ばれます。
メソッド exit の説明は次のとおりです。
実行時に return 命令が発生すると、戻り値が上位のメソッド呼び出し元に渡されます。この exit メソッドは、一般的に言えば、通常のメソッド呼び出し完了と呼ばれます。呼び出し元の PC カウンタを戻りアドレスとして使用できます。
実行中に例外が発生し、現在のメソッド本体が処理されない場合、メソッドは終了します。これは、突然のメソッド呼び出し完了と呼ばれます。 Exception Processor テーブルを渡して決定します。
2 ローカルメソッドスタック
ローカルメソッドスタックと仮想マシンスタックは基本的に似ていますが、仮想マシンスタックがクラスバイトコードを実行するのに対し、ローカルメソッドスタックは実際にはローカルメソッドサービスを呼び出すことになります。異なる OS プラットフォームに応じて、C または C++ で記述された同じメソッドのいくつかの異なる実装。3 メソッド領域
メソッド領域はスレッドによって共有される領域で、仮想マシンによってロードされたクラス情報(クラスのバイトコードデータ。ここで注意してください。クラスが多すぎる場合は、メソッド領域のスペースを増やす必要があります。そうしないと、クラスが少ない場合にのみ OOM が発生します。クラスが多すぎる場合は、遅延読み込みやその他のメカニズムを使用できます。 Spring の遅延ロード機構などを使用して、多くのクラス ()、定数、静的変数、およびジャストインタイム コンパイラ (JIT) でコンパイルされたコードやその他のデータを同時にロードしないようにしてください。メソッド領域は実際には永続世代領域と呼ばれるものです (ホットスポット仮想マシンの実装メカニズムに限定されます)。これが永続世代と呼ばれる理由は、ロードされたクラスがロードされるため、ここでのデータがほとんどガベージ コレクションされないためです。多くのメソッドはクラスに基づいてヒープ内にオブジェクトを作成しますが、定数はまったく変更されないデータであるため、ほとんど使用されません。 。 掃除。
Java 仮想マシンの仕様では、この領域に関して非常に緩やかな制限があり、物理的に不連続なスペースであることに加えて、固定サイズとスケーラビリティも可能であり、ガベージ コレクションを実装する必要がありません。比較的言えば、この領域ではガベージ コレクションの動作は比較的まれです (そのため、定数と静的変数の定義にはより注意を払う必要があります)。メソッド領域でのメモリ収集は引き続き発生しますが、この領域でのメモリ収集は主に定数プールのリサイクルと型のアンロードのために行われます。
一般に、メソッド領域でのメモリの再利用を満足させるのは困難です。メソッド領域がメモリ割り当て要件を満たせない場合、OutOfMemoryError 例外がスローされます。
4 実行時定数プール
JDK1.6以前 String定数プールはメソッド領域にありました。
JDK1.7の文字列定数プールがヒープに移動されました。
Javaは動的に接続された言語であり、コード内で定義されているさまざまな基本型(int、longなど)やオブジェクト型(など)に加えて、定数プールの役割が非常に重要です。 String と Array の定数値には、次のようなテキスト形式のシンボル参照も含まれます。
クラスとインターフェイスの完全修飾名
メソッドと名前と記述子。 。
C言語
では、プログラムが別のライブラリの関数を呼び出したい場合、接続時にライブラリ内の関数の位置(つまり、ライブラリファイルの先頭からの相対的なオフセット)がプログラムに記述され、実行時にこのアドレスに直接アクセスして関数を呼び出します このように、Java 言語ではすべてが動的です。コンパイル時に他のクラス メソッドの呼び出しまたは他のクラス フィールドへの参照が見つかった場合、クラス ファイルに記録されるのはテキスト形式のシンボル参照のみであり、接続プロセス中に仮想マシンはこのテキストに基づいて検索します。対応するメソッドまたはフィールドの情報。
したがって、Java 言語のいわゆる「定数」とは異なり、クラスファイル内の「定数」の内容は非常に豊富であり、これらの定数はクラス内で呼び出される領域に次々と集中しています。 「定数プール」
Java の定数プール技術は、特定のオブジェクトを便利かつ迅速に作成するようで、オブジェクトが必要になったときに、それをプールから取り出し (プールに誰もいない場合は作成し)、必要に応じてそれを繰り返すことができます。等しい変数を作成するときに時間を大幅に節約できます。定数プールは実際にはメモリ空間であり、
newキーワードを使用して作成されたオブジェクトが配置されるヒープ空間とは異なります。 定数プール全体は、JVM のインデックスによって参照されます。配列内の要素のセットがインデックスに従ってアクセスされるのと同じように、JVM もこれらの定数プールに格納されている情報をインデックス メソッドに従って処理します。実際、定数プールは Java プログラム内で動的にリンクされています。プロセス
は重要な役割を果たします (前述)。以下は「Java 仮想マシンの詳細」からの抜粋です。クラスのバージョン、フィールド、メソッド、インターフェイス、その他の情報に加えて、定数プールがコンパイラによって生成されたさまざまなリテラルやシンボル参照を保存するために使用されるという情報もクラスファイルに含まれます。クラスがロードされた後にロードされ、メソッド領域のランタイム定数プールに格納されます。 Java 仮想マシンには、クラスのあらゆる部分 (定数プールを含む) について厳しい規制があり、各バイトを格納するためにどのような種類のデータが使用されるかについては、仮想マシンが認識、ロード、実行できるようにするための仕様要件が必要です。一般に、クラス ファイルに記述されているシンボル参照の保存に加えて、変換された直接参照も実行時定数プールに保存されます。
クラス ファイル定数プールと比較したランタイム定数プールのもう 1 つの重要な特徴は、Java 仮想マシンでは、定数がコンパイル中にのみ生成される必要がなく、クラスに事前設定されていないコンテンツであることです。ファイル定数プールをメソッド領域の実行時定数プールに入力することもでき、実行時に新しい定数を定数プールに入れることもできます。 定数プールはメソッド領域の一部であるため、メモリによって制限され、十分なメモリを適用できない場合、 OutOfMemoryError 例外がスローされます5 ヒープ
ヒープはメモリ内の最大の領域です。オブジェクトインスタンスが保存される場所。ここはGCアルゴリズムの主戦場でもある。ただし、JIT (ジャスト イン タイム コンパイル) の発展とエスケープ テクノロジの成熟により、すべてのオブジェクトがヒープ上に作成されるわけではなくなりました。以下は、「Java 仮想マシンの徹底理解」からの抜粋です。
Java プログラミング言語および環境において、ジャストインタイム コンパイラー (JIT コンパイラー) は、Java バイトコード (解釈が必要な命令を含む) を、Java の処理プログラムに直接送信できるプログラムに変換するプログラムです。デバイスの説明書。 Java プログラムを作成する場合、ソース言語ステートメントは、特定のプロセッサ ハードウェア プラットフォーム (たとえば、Intel の Pentium マイクロプロセッサや IBM の System/390 プロセッサ) に対応する命令コードにコンパイルされるのではなく、Java コンパイラによってバイトコードにコンパイルされます。バイトコードは、プラットフォームに依存しないコードであり、任意のプラットフォームに送信して、そのプラットフォーム上で実行できます。
Javaのメモリ割り当ては大体こんな感じです。jvmには上記のデータを調整するためのパラメータも多数用意されています。ここでは記載しませんが、gc関連の別記事で詳しく解説します。マルチコアプロセッサの時代に同時実行性によって引き起こされる問題を JVM がどのように処理するかについて話しましょう。
同時実行制御
マルチコアCPUは複数のスレッドを同時に実行でき、各スレッドは独自のローカルワークスペース(実際には各コアに割り当てられたシステムキャッシュとレジスタ)を持ち、メインから取得したデータを保存しますデータが複数のスレッド間で共有され、スレッド間でデータを交換できない場合、共有変数内のデータが不整合になるという問題が発生します。 Java は、同期された揮発性ロックなどのメカニズムを通じて共有変数の可視性を制御します。
Synchronized と lock はそれぞれ別の章で実装メカニズムを説明します。言うまでもなく、これら 2 つは可視性とアトミック性の点で保証されています。 Volatile はデータの可視性のみを保証します。この 2 つの段階を通過すると、データが読み取られてロードされるときにのみ、他のスレッドでのデータの変更が検出されます。 実際、volatile が行うことは、キャッシュの使用を回避し、メイン メモリ上のデータをスレッドの作業メモリに保存しないことです。読み取りおよびロードの段階で、他のスレッドが認識できるようにデータがメイン メモリから取得されます。変数 )。 Volatile は、初期の JDK バージョンの synchronized のパフォーマンスが低いため、可視性を確保するための単なるソリューションです。 synchronized と lock の現在の JDK バージョンはある程度最適化されているため、現時点で volatile を使用していることがわかっていない限り、volatile 変数を使用することは一般に推奨されません。これは同時実行の正確性が保証されないためです。
メインメモリから現在のワーキングメモリに変数をコピーして読み込む、使用して割り当てる、コードを実行して共有変数の値を変更する、使用と割り当てが表示されるメインメモリ関連のコンテンツをリフレッシュするために使用するワーキングメモリデータを保存して書き込む複数回 。 volatile は、いくつかの冪等な操作に適しています。これについては、ロックの nofairsync の実装で説明します。
この時点で、事前に起こる原理について話さなければなりません。 Java メモリ モデルで定義された 2 つの操作間の半順序関係です。操作 A が操作 B の前に発生する場合、操作 B が発生する前に、操作 A の影響が操作 B によって観察されることを意味します。「影響」には、変更が含まれます。メモリ内の共有変数の値、メッセージの送信、メソッドの呼び出しなど。基本的に、時間内のイベントのシーケンスとは何の関係もありません。この原則は、データ内に競合があるかどうか、およびスレッドが安全かどうかを判断するための主な基礎として特に重要です。
以下は、hack-before を保証する Java メモリ モデルの 8 つのルールであり、シンクロナイザーの助けなしですでに存在しており、コーディングで直接使用できます。 2 つの操作間の関係がこのリストになく、次のルールから推測できない場合、順序の保証はなく、仮想マシンはそれらの順序をランダムに並べ替えることができます (CPU を最大限に活用して使用率を向上させるため、 jvm 、 JVM無関係なコードや操作の順序が変更され、IO やその他のリソースを待つ必要がある操作が最後に配置され、即座に完了できる他の操作が前に配置されて最初に実行され、CPU リソースを最大限に活用します)。
1. プログラム シーケンス ルール: 別のスレッドでは、プログラム コードの実行フロー順序に従って、(時間的に) 最初に実行される操作が、(時間的に) 後で実行される操作より前に発生します。
2. 管理ロック ルール: ロック解除操作は、同じロックに対するロック操作よりも前に (時系列で、以下同様) 行われます。
3. Volatile変数ルール: volatile 変数への書き込み操作は、その後の変数への読み取り操作の前に発生します。
4. スレッド起動ルール: Thread オブジェクトの start() メソッドは、このスレッドのすべてのアクションの前に発生します。
5. スレッド終了ルール: スレッドのすべての操作は、このスレッドの終了が検出される前に発生します。Thread.join() メソッドの終了、つまり Thread の戻り値によって、スレッドが実行を終了したことを検出できます。 isAlive() など
6. スレッド中断ルール: スレッド中断() メソッドの呼び出しは、中断されたスレッドのコードが中断を検出して イベント が発生する前に行われます。
7. オブジェクトの終了ルール: オブジェクトの初期化は、finalize() メソッドの開始前に完了します (コンストラクターの実行が終了します)。
8. 推移性: 操作 A が操作 B の前に発生し、操作 B が操作 C の前に発生した場合、A が操作 C の前に発生することを取得できます。
以上がJMM Java メモリ モデルの詳細な図による説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。