Java 仮想マシンについて学ぶ必要があるのはなぜですか? 1. Java が下位レベルでどのように動作するかをより明確に理解する必要があります。これにより、Java をより深く学習できるようになります。 2. エラーのデバッグに関する貴重な経験を提供してください。 3. これは有能な Java プログラマーが知っておくべきものです。 この記事では、JVM の内部構造について説明し、コンポーネント内のマルチスレッド処理、JVM システム スレッド、ローカル変数配列などの側面から分析します。
仮想マシン JVM = クラスローダー + 実行エンジン + ランタイムデータ領域 次の図は、一般的な JVM (JVM 仕様 Java SE 7 エディションに準拠) の主要な内部コンポーネントを示しています。 コンポーネントのマルチスレッド 「マルチスレッド」または「フリースレッド」とは、プログラムが複数のスレッドの操作を同時に実行できる機能を指します。マルチスレッド アプリケーションの例として、プログラムは 1 つのスレッドでユーザー入力を受け取り、別のスレッドでいくつかの複雑な計算を実行し、3 番目のスレッドでデータベースを更新します。シングルスレッド アプリケーションでは、ユーザーは計算またはデータベースの更新が完了するまで待機する時間がかかる場合があります。マルチスレッド アプリケーションでは、これらのプロセスはバックグラウンドで実行されるため、ユーザーの時間が無駄になりません。マルチスレッドは、コンポーネントプログラミングにおいて非常に強力なツールになります。マルチスレッド コンポーネントを記述することで、バックグラウンドで複雑な計算を実行するコンポーネントを作成し、計算の進行中にユーザー インターフェイス (UI) がユーザー入力に自由に応答できるようになります。マルチスレッドは強力なツールですが、正しく使用するのは難しいです。マルチスレッド コードが誤って実装されると、アプリケーションのパフォーマンスが低下したり、アプリケーションがフリーズしたりする可能性があります。次のトピックでは、マルチスレッド プログラミングに関する考慮事項とベスト プラクティスについて説明します。 .NET Framework には、コンポーネントのマルチスレッド化のためのいくつかのオプションが用意されています。 System.Threading 名前空間の関数は 1 つのオプションです。イベントベースの非同期パターンも別のオプションです。 BackgroundWorker コンポーネントは非同期パターンの実装です。使いやすさを考慮してコンポーネントにカプセル化された高度な機能を提供します。 JVM メモリ管理メカニズム (1)メモリ領域とメモリオーバーフロー例外 (2)ガベージコレクタとメモリ割り当て戦略 (3)仮想マシンのパフォーマンス監視およびトラブルシューティングツール JVM チューニング 1.JVM実行サブシステム (1)クラスファイルの構造 (2)クラスローディング機構 (3)バイトコード実行エンジン 2. プログラムのコンパイルとコードの最適化 (1)コンパイル時の最適化 (2)ランタイム最適化 3. 実践的なチューニング事例とソリューション JVM システムスレッド jconsole またはその他のデバッグ ツールを使用して確認すると、バックグラウンドで多数のスレッドが実行されていることがわかります。これらの実行中のバックグラウンド スレッドには、public static void main(String[]) を実行する必要性に基づいて作成されるメイン スレッドは含まれません。これらのバックグラウンド スレッドはメイン スレッドによって作成されます。 HotspotJVM の主なバックグラウンド システム スレッドを次の表に示します。 シングルスレッド 各スレッドの実行は、次のコンポーネントで構成されます。 プログラムカウンタ (PC) 現在の命令またはオペコードがネイティブでない限り、現在の命令またはオペコードのアドレスは、アドレス指定のために PC に依存する必要があります。現在のメソッドがネイティブの場合、PC は未定義になります。すべての CPU には PC があり、通常、各命令の実行後に増分されて、次に実行される命令のアドレスを指します。 JVM は PC を使用して、実行されている命令の場所を追跡します。実際、PC はメソッド領域のメモリ アドレスを指すために使用されます。 ネイティブスタック すべての JVM がネイティブ メソッドをサポートしているわけではありませんが、ネイティブ メソッドをサポートする JVM は通常、スレッドごとにネイティブ メソッド スタックを作成します。 C リンク モデルを使用して JVM の JNI (Java Native Invocation) を実装する場合、ネイティブ スタックも C で実装されたスタックになります。この例では、ネイティブ スタック内のパラメーターの順序と戻り値は、通常の C プログラムと同じになります。ネイティブ メソッドは通常、JVM へのコールバック (これは JVM 実装によって異なります) を行い、Java メソッドを実行します。このようなネイティブから Java への呼び出しはスタック (通常は Java スタック) 上で発生し、同時にスレッドもネイティブ スタックを離れ、通常は Java スタック上に新しいフレームを作成します。 スタック 各スレッドには独自のスタックがあり、スレッドで実行される各メソッドのフレームを格納するために使用されます。スタックは後入れ先出しのデータ構造であり、現在実行中のメソッドをスタックの一番上に配置できます。メソッドの実行ごとに、新しいフレームが作成され、スタックの先頭にプッシュされます。メソッドが正常に返されるか、メソッドの実行中にキャッチされない例外が発生すると、フレームがポップされます。フレーム オブジェクトのプッシュ/ポップを除き、スタックは直接操作されません。したがって、フレーム オブジェクトはヒープ上に割り当てられる可能性があり、メモリは必ずしも連続したアドレス空間である必要はないことがわかります (フレーム ポインタとフレーム オブジェクトの違いに注意してください)。 スタックの制限 スタックは動的または固定サイズにすることができます。スレッドがより大きなスタックを要求すると、StackOverflowError 例外がスローされます。スレッドが新しいフレームの作成を要求したが、それを割り当てるのに十分なメモリ領域がない場合は、OutOfMemoryError 例外がスローされます。 フレーム メソッドの実行ごとに、新しいフレームが作成され、スタックの先頭にプッシュされます。メソッドが正常に返されるか、メソッドの実行中にキャッチされない例外が発生すると、フレームはスタックからポップされます。 ローカル変数配列 ローカル変数配列には、メソッドの実行中に使用されるすべての変数が含まれます。これ、すべてのメソッド パラメーター、およびその他のローカルに定義された変数への参照が含まれます。クラス メソッド (静的メソッドなど) の場合、メソッド パラメーターのストレージ インデックスは 0 から始まります。インスタンス メソッドの場合、インデックス 0 のスロットは this ポインターを格納するために予約されています。 オペランドスタック オペランド スタックは、バイトコード命令の実行中に使用されます。これは、ネイティブ CPU で使用される汎用レジスタに似ています。バイトコードの大部分は、プッシュ、ポップ、コピー、スワップ、または値の生成/消費操作の実行によって、オペランド スタックとのやり取りに時間を費やします。バイトコードの場合、ローカル変数配列とオペランドスタック間で値を移動する命令が非常に頻繁に使用されます。 動的リンク 各フレームには、ランタイム定数プールへの参照が含まれています。この参照は、実行されるメソッドが属するクラスの定数プールを指します。この参照は動的リンクを支援するためにも使用されます。 Java クラスがコンパイルされると、クラスの定数プールに格納されている変数とメソッドへのすべての参照は、シンボリック参照として扱われます。シンボリック参照は単なる論理参照であり、最終的に物理メモリ アドレスを指す参照ではありません。 JVM 実装では、シンボリック参照をいつ解決するかを選択できます。これは、クラス ファイルが検証され、ロードされた後に発生する可能性があり、これは積極的分析または静的分析と呼ばれます。違いは、シンボリック参照が初めて使用されるときにも発生する可能性があることです。これは、遅延分析または遅延分析と呼ばれます。ただし、JVM では、各参照が初めて使用される前に解決が行われ、その時点で分析エラーが発生した場合に例外がスローされることを保証する必要があります。バインディングとは、シンボリック参照によって識別されるフィールド、メソッド、またはクラスを直接参照に置き換えるプロセスです。シンボル参照を完全に置き換える必要があるため、このプロセスは 1 回だけ実行されます。シンボリック参照がクラスを参照し、そのクラスがまだ解決されていない場合は、そのクラスもすぐにロードされます。各直接参照は、変数またはメソッドの実行時の場所に関連付けられた構造体へのオフセットとして保存されます。 スレッド間で共有 ヒープ
最大のルート ノードを持つヒープは最大ヒープまたはビッグ ルート ヒープと呼ばれ、最小のルート ノードを持つヒープは最小ヒープまたはスモール ルート ヒープと呼ばれます。一般的なヒープには、バイナリ ヒープとフィボナッチ ヒープがあります。 ヒープ定義は次のとおりです: n 個の要素のシーケンス {k1、k2、ki、…、kn} は、次の関係を満たす場合にのみヒープと呼ばれます。
このシーケンスに対応する 1 次元配列 (つまり、1 次元配列がこのシーケンスの格納構造として使用されている) を完全な二分木と見なすと、ヒープの意味は、完全な二分木内のすべての非終端ノードの値が、その左右の子ノードの値より大きくない (または小さくない) ことを示します。したがって、シーケンス {k1、k2、...、kn} がヒープである場合、ヒープの最上位要素 (または完全なバイナリ ツリーのルート) は、シーケンス内の n 個の要素の最小値 (または最大値) である必要があります。 非ヒープメモリ 一部のオブジェクトはヒープ内に作成されず、これらのオブジェクトは論理的には JVM メカニズムの一部であると見なされます。 非ヒープ メモリには次のものが含まれます。
メモリ管理 オブジェクトと配列は明示的に解放されることはないため、ガベージ コレクターによってのみ自動的に回収されます。 通常、手順は次のとおりです。 新しいオブジェクトと配列は若い世代で作成される マイナー ガベージ コレクターは若い世代で実行されます。まだ生きているオブジェクトはエデンエリアからサバイバーエリアに移動されます メジャー ガベージ コレクターはオブジェクトを世代間で移動し、通常、メジャー ガベージ コレクターによってアプリケーションのスレッドが一時停止されます。まだ生きているオブジェクトは、若い世代から古い世代に移動されます。 古い世代がリサイクルされるたびに、永久世代もリサイクルされます。どちらかがいっぱいになるとリサイクルされます。 JIT コンパイル JIT の具体的な操作は次のとおりです。型が読み込まれると、CLR は型の内部データ構造と対応する関数を作成します。関数が初めて呼び出されると、JIT は関数を機械語にコンパイルします。関数が再度実行されると、コンパイルされたマシン言語がキャッシュから直接実行されます。 方法領域 すべてのスレッドは同じメソッド領域を共有します。したがって、メソッド領域データへのアクセスと動的リンクの処理はスレッドセーフである必要があります。 2 つのスレッドが、まだロードされていないクラス (クラスは 1 回だけロードする必要があります) のフィールドまたはメソッドにアクセスしようとすると、クラスがロードされるまで 2 つのスレッドは実行を続行できません。 クラスファイルの構造 コンパイルされたクラス ファイルの構造は次のとおりです。
javap コマンドを使用して、コンパイルされた Java クラスのバイトコードを表示できます。 以下は、このクラス ファイルで使用されるオペコードのリストです。 他の一般的なバイトコードと同様に、上記のオペコードは主にローカル変数、オペランド スタック、およびランタイム定数プールを処理するために使用されます。 コンストラクターには 2 つの命令があり、最初の命令は「this」をオペランド スタックにプッシュし、次にコンストラクターの親コンストラクターが実行されて this が「消費」され、オペランド スタックからポップされます。 sayHello() メソッドに関しては、実行がより複雑になります。ランタイム定数プールを通じて、シンボリック参照を実際の参照に解決する必要があるためです。最初のオペランド getstatic は、System クラスから静的フィールドへの参照をオペランド スタックにプッシュするために使用されます。次のオペランド ldc は、文字列リテラル「Hello」をオペランド スタックにプッシュします。最後に、invokevirtual オペランドは System.out の println メソッドを実行し、オペランド スタックから "Hello" をパラメーターとしてポップし、現在のスレッドの新しいフレームを作成します。 高並列性、分散、Springソースコード、MyBatisソースコード、ビッグデータ、Nettyなどの技術的な知識ポイントの包括的なアーキテクチャビデオ資料 クラスローダー JVM の起動は、ブートストラップ クラス ローダーを通じて初期化用のクラスをロードすることです。このクラスは、public static void main(String[]) が実行される前にリンクされ、インスタンス化されます。メイン メソッドを実行すると、追加の必要なクラスとインターフェイスが順番に読み込まれ、リンクされ、初期化されます。 ロード: ロードとは、クラスまたはインターフェース タイプを表すクラス ファイルを見つけて、それをバイト配列に読み込むプロセスです。次に、バイトが解析され、クラス オブジェクトを表しているかどうか、およびメジャー バージョン番号とマイナー バージョン番号が正しいかどうかが確認されます。直接のスーパークラスと見なされるクラスまたはインターフェースもすべてロードされます。これが完了すると、バイナリ表現からクラスまたはインターフェース オブジェクトが作成されます。 リンク: リンクには、クラスまたはインターフェースの検証、準備された型、クラスの直接の親クラスと親インターフェースが含まれます。つまり、リンクには検証、準備、解析(オプション)の3つのステップが含まれます。 検証: このフェーズでは、クラスとインターフェースの表現が構造的に正しく、Java プログラミング言語と JVM セマンティクスの要件を満たしていることを確認します。 検証段階でこれらのチェックを実行すると、実行時のリンク段階でこれらのアクションを回避できますが、クラスの読み込みが遅くなりますが、バイトコードの実行時にこれらのチェックが実行されなくなります。 準備: これには、静的ストレージと JVM によって使用されるデータ構造 (メソッド テーブルなど) のメモリ割り当てが含まれます。静的フィールドはデフォルト値で作成され、インスタンス化されます。ただし、これらのタスクはインスタンス化フェーズ中に実行されるため、このフェーズではインスタンス化子またはコードは実行されません。 解析: オプションのフェーズです。このフェーズでは、参照されているクラスまたはインターフェースをロードして、シンボル参照が正しいかどうかを確認します。この時点でこれらのチェックが行われない場合、シンボリック参照の解決はバイトコード命令によって使用される直前まで延期されます。 クラスまたはインターフェースのインスタンス化メソッドの実行を含め、クラスまたはインターフェースをインスタンス化します。 JVM には、異なる役割を持つ複数のクラス ローダーが存在します。各クラス ローダーは、その親クラス ローダーに委任します (ルート クラス ローダーであるブートストラップ クラス ローダーを除く)。 ブートストラップ クラス ローダー: Java プログラムの実行中、Java 仮想マシンは Java クラスをロードする必要があり、このプロセスを完了するにはクラス ローダーが必要です。クラスローダー自体も Java クラスであるため、人類の最初の母親がどのように生まれたのかという疑問と同様の疑問が生じます。 実際、Java 仮想マシンには Bootstrap と呼ばれるクラス ローダーが組み込まれており、これはオペレーティング システム固有のネイティブ コードで実装され、Java 仮想マシンのカーネルに属します。この Bootstrap クラス ローダーをロードするには、特別なクラス ローダーは必要ありません。 Bootstrap クラス ローダーは、Java コア パッケージ内のクラスをロードする役割を担います。 拡張クラス ローダー: 標準の Java 拡張 API からクラスをロードします。たとえば、セキュリティのための拡張機能セットなどです。 システム クラス ローダー: これはアプリケーションのデフォルトのクラス ローダーです。クラスパスからアプリケーション クラスを読み込みます。 ユーザー定義のクラスローダー: アプリケーションクラスをロードするためのクラスローダーを追加で定義できます。ユーザー定義のクラス ローダーは、実行時にクラスを再ロードしたり、一部の特殊なクラスを複数の異なるグループに分離したりするなどの特殊なシナリオで使用できます (通常、Tomcat などの Web サーバーではこのような要件があります)。 より高速なクラス読み込み HotspotJVM 5.0 では、クラス データ共有 (CDS) と呼ばれる機能が導入されました。 JVM のインストール中に、インストーラーは共有アーカイブ用にマップされたメモリ領域に Java コア クラスのセット (rt.jar など) をロードします。 CDS はこれらのクラスのロード時間を短縮し、JVM の起動速度を向上させるとともに、これらのクラスを異なる JVM インスタンス間で共有できるようにします。これにより、メモリの断片化が大幅に減少します。 メソッドエリアの場所 JVM 仕様 Java SE 7 エディションでは、次のように明確に述べられています。メソッド領域はヒープ内の論理的な一部ですが、最も単純な実装は、ガベージ コレクションも圧縮も行わないことです。しかし、矛盾は、jconsole を使用して Oracle の JVM メソッド領域 (および CodeCache) を表示すると、非ヒープ形式になることです。 OpenJDK コードは、CodeCache が ObjectHeap に対して VM 内の独立したドメインであることを示しています。 クラスローダー参照 クラスは通常、オンデマンドでロードされます。つまり、クラスが初めて使用されるときにロードされます。クラスローダーのおかげで、Java ランタイムシステムはファイルとファイルシステムについて知る必要がありません。 ランタイム定数プール JVM は各タイプに対して定数プールを維持します。これはシンボル テーブルに似たランタイム データ構造ですが、より多くのデータが含まれています。 Java バイトコードには、バイトコードに直接保存するには通常は大きすぎるデータが必要です。代わりに、定数プールに格納し、バイトコードに定数プールへの参照を含めるという方法もあります。ランタイム定数プールは主に動的リンクに使用されます。 定数プールには、次のようないくつかの種類のデータが格納されます。
次の単純なクラスをコンパイルすると:
生成されたクラス ファイルの定数プールは次の図のようになります。
定数プールには次のタイプが含まれます。 例外テーブル 例外テーブルには、各例外ハンドラに関する情報が格納されます。
メソッドが try-catch または try-finally 例外ハンドラーを定義すると、例外テーブルが作成されます。これには、処理される例外の種類とハンドラー コードの場所とともに、各例外ハンドラーまたは finally ブロックに関する情報が含まれます。 例外がスローされると、JVM は現在のメソッドに一致するハンドラーを探します。見つからない場合、メソッドは最終的に現在のスタックフレームを突然ポップし、例外が呼び出しチェーン (新しいフレーム) に再スローされます。すべてのフレームがスタックからポップされる前に例外ハンドラが見つからない場合、現在のスレッドは終了します。もちろん、メイン スレッドなどの最後の非バックグラウンド スレッドで例外がスローされた場合、JVM が終了する可能性もあります。 最終的な例外ハンドラはすべての例外タイプに一致し、そのタイプの例外がスローされるたびに常に実行されます。例外がスローされない場合でも、メソッドの最後に finally ブロックが実行されます。 return ステートメントが実行されると、すぐに finally コード ブロックにジャンプして実行を続行します。 キャラクター比較 文字比較とは、辞書の順序で単一の文字または文字列のサイズを比較する操作を指します。一般的に、文字の比較の基準として ASCII コード値のサイズが使用されます。 シンボルテーブル コンパイルプロセス中、シンボルテーブルは、ソースプログラム内の一部の文法シンボルの種類や特性などの関連情報を継続的に収集、記録、使用する必要があります。この情報は通常、表形式でシステムに保存されます。定数テーブル、変数名テーブル、配列名テーブル、手続き名テーブル、ラベルテーブルなどを総称してシンボルテーブルと呼びます。シンボルテーブルの構成、構築、管理方法の品質は、コンパイルシステムの動作効率に直接影響します。 JVM では、内部文字列は文字列テーブルに保存されます。文字列テーブルは、オブジェクト ポインターをシンボルにマッピングするハッシュ テーブルです (例: 文字列リテラルはコンパイラによって自動的に「内部化」され、クラスがロードされるときに文字列テーブルに追加されます。さらに、String.intern() を呼び出すことによって、文字列クラスのインスタンスを明示的に内部化できます。 String.intern() が呼び出されたときに、シンボル テーブルにすでに文字列が含まれている場合は、その文字列への参照が返されます。文字列が文字列テーブルに含まれていない場合は、文字列テーブルに追加され、その参照が返されます。 |
<<: Ping An Cloud が、企業の効率的なクラウド移行を支援するマルチクラウド管理で新たな賞を受賞
>>: アリババから第三層企業まで、大規模分散システムにおけるキャッシュの応用
中国のウェブサイトユーザーを見ると、多くのウェブサイト運営者がホームページを中心に考えていることがわ...
データ管理ベンダー Cohesity の新しい調査レポートによると、IT マネージャーがパブリック ...
クラウド + DevOps = より迅速なデジタル変革。クラウドはスケーラビリティと柔軟性を提供し、...
今年、教育とトレーニングのウェブサイトが素晴らしい開発の可能性を秘めていると思いました私は何十人もの...
ミドルウェア業界の著名なメーカーである北京 Tong Technology Co., Ltd.(以下...
長らく収益モデルに制約されてきた中国のオンライン音楽業界は、新たなビジネスモデル革新を起こしつつある...
今年 10 月、virpus は最新の人気プロモーションを実施し、すべての Xen PV 仮想 VP...
月収10万元の起業の夢を実現するミニプログラム起業支援プランインターネットの人口ボーナスが徐々に消滅...
メディアの報道によると、WeChatのユーザー数は6億人を突破しました!近い将来、WeChatは重要...
A400 Interconnectの米国VPSには、通常のBGPと米国Unicom AS9929のハ...
BandwagonHost VPS は、低価格、高速、使いやすさで常に知られています。ただし、Ban...
GinerNet は、RIPE メンバー (こちらを参照) + AS59432 (こちらを参照) と...
[[373788]]コンピューティングの今後とそれが組織の戦略にどのような影響を与えるでしょうか?専...
SAPは先日、世界経済の不確実性が企業のデジタル変革を加速させ続ける中、2020年第3四半期に280...
本日、2018年杭州雲奇カンファレンスにおいて、アリババグループとSAPは共同で、顧客のクラウド移行...