私のJVM研究ノート:第3章-実行時データ領域(1)

私のJVM研究ノート:第3章-実行時データ領域(1)

熟練したコースへのJVMエントリについてShang SiguのSong Hongkang氏に感謝し、無料コースを注意深く教えるすべての教師に敬意を表します!
このチュートリアルのセットは、コースを終えた後の私の学習ノートです。忘れないようにして、みんなに送って共有してください。見てくれてありがとう〜

この章には知識ポイントが含まれています:実行データ領域の概要、プログラムカウンターの詳細な説明、仮想マシンスタックの概要、仮想マシンスタックの実行プロセス、仮想マシンスタックでのStackOverFlowErrorとOutOfMemoryError、および仮想マシンスタックのサイズの設定方法!

1.実行時データ領域構造

注:メモリ管理のメカニズムはJVMによって異なりますが、ここではホットスポット仮想マシンのメモリ構造についてのみ説明します。
公式のランタイムデータ領域の構造を見てみましょう。
実行時データ領域の構造
ランタイムデータ領域は主に次の部分で構成されています。

  • メソッド領域:クラスが読み込まれた後の情報はメソッド領域に格納されます
  • ヒープ:オブジェクトデータの格納を担当
  • プログラムカウンター:コードの実行場所を記録するために使用されます
  • ローカルメソッドスタック:仮想マシンスタックに似ており、他の言語で書かれたメソッドの実行を担当します(C言語メソッドの呼び出しなど)
  • 仮想マシンスタック:スタックフレームはメソッドに対応し、仮想マシンスタックは、メソッドが実行されたときの対応するJVMのメモリモデルを記述します(メソッドは仮想マシンスタックの実行エンジンによって実行されます)。

詳細な凡例:
JVMランタイムデータ領域
注:JDK8以降、HotSpot仮想マシンはメソッド領域ではなくメタ空間を使用します。2つの機能と構造は基本的に同じです!

JVMのマルチスレッド処理メカニズム:
スレッドは、プログラムの実行単位です。JVMでは、アプリケーションを複数のスレッドで並行して実行できます。
Javaスレッドとネイティブスレッドの関係:
HotspotJVMでは、各スレッドはオペレーティングシステムのネイティブスレッドに直接マップされます。Javaスレッドを実行する準備ができると、オペレーティングシステムのネイティブスレッドも同時に作成されます。Javaスレッドの実行が終了すると、ローカルスレッドもリサイクルされます。オペレーティングシステムは、使用可能なCPUへのすべてのスレッドのスケジューリングを担当します。ローカルスレッドが正常に初期化されると、Javaスレッドでrun()メソッドが呼び出されます。最後の非デーモンスレッドが終了すると、JVMも終了します。最初の図に示されて
いるランタイムデータ領域と既存の関係の構造

赤い領域(ヒープ領域、メソッド領域):スレッド間で共有され、JVMプロセスには1つのコピーしか存在しません。
灰色の領域(プログラムカウンター、ローカルメソッドスタック、仮想マシンスタック):スレッドに依存しません。つまり、プロセスに5つのスレッドがある場合、プログラムカウンター、ローカルメソッドスタック、仮想マシンスタックの5つのポイントがあります。そして、スレッドが作成されると作成され、スレッドが破棄されると破棄されます!

結論として:
JVMのランタイムデータ領域は、ヒープ領域、メソッド領域、プログラムカウンター、ローカルメソッドスタック、仮想マシンスタックの5つの部分で構成されています。
その中でも、ヒープ領域とメソッド領域はスレッド間で共有され、プログラムカウンター、ローカルメソッドスタック、仮想マシンスタックはスレッド間でのみ共有されます。

2.プログラムカウンター

プログラムカウンタレジスタ(Program Counter Register)は、プログラムカウンタ、PCレジスタと略されます。プログラムカウントレジスタは、ハードウェアの意味でのレジスタではなく、物理レジスタの抽象化です。(プログラムフック/行番号インジケーターとして理解できます)、プログラム全体で最速のメモリ領域です。現在のスレッドで実行されているJavaプログラムの次の命令のアドレスを記録するために使用されます(次の行で実行されるコードの行番号)、実行エンジンは次の命令を読み取って実行できます。

要約
すると、これはプログラム制御フローのインジケータであり、分岐、ループ、ジャンプ、例外処理、スレッド回復などの基本的な機能はすべて、完了するためにこのカウンタに依存する必要があります。

注意:

  • 各スレッドには独立したプログラムカウンターがあります並列スレッドプライベート、ライフサイクルはスレッドと一致しています!
  • Nativeメソッドが現在実行されている場合、プログラムレジスタは効果がなく、値は常に未定義です!
  • バイトコードインタープリターが動作すると、このカウンターの値を変更することにより、実行する次のバイトコード命令を選択します。
  • これは、Java仮想マシン仕様でOutOtMemoryError条件を指定していない唯一の領域です。

質問:なぜプログラムカウンタを使用して行番号を記録するのですか?
CPUには常にスレッドを切り替える、この時点で切り替えた後、次のことを知る必要があります実行を継続する場所JVMワードセクションコードインタープリター次に実行するバイトコード命令を決定するには、PCレジスタの値を変更する必要があります。

質問2:プログラムカウンターをスレッドプライベートとして設定する必要があるのはなぜですか?
私たちは皆知っていますいわゆるマルチスレッドメソッドは、特定の期間内に1つのスレッドのみを実行します。、CPUは引き続き実行しますタスクの切り替え、これは必然的に頻繁な中断または回復につながりますが、ポイントに違いがないことを確認するにはどうすればよいですか?各スレッドによって実行されている現在のバイトコード命令アドレスを正確に記録するために、最善の方法は当然です各スレッドはPCレジスタを割り当てます、各スレッドを独立した計算、相互干渉がないように。

例:プログラムの行番号をバイトコードで表示する

public class TestPCRegister {
    
    
    public static void main(String[] args) {
    
    
        int a = 10;
        int b = 20;
        int c = a + b;
        System.out.println(c);
    }
}

逆コンパイルコマンド:

javap –v 文件名

この時点で、バイトコードファイルのコードの各行の前に対応する位置があることがわかります行番号、これらの行番号はプログラムカウンタに格納されている内容です。行が実行されると、実行エンジンはコード行番号の次の行CPUタイムスライスが奪われても、実行がいつ続くかを判断できるように、プログラムカウンターを書き込みます。実行されるコードの場所、プログラムがうまくいかないことを確実にするために!
逆コンパイル結果

3.仮想マシンスタックの概要


Java仮想マシンスタックとは何ですか:Java仮想マシンスタック(Java仮想マシンスタック)。初期の頃はJavaスタックとも呼ばれていました。各スレッドは、作成時に仮想マシンスタックを作成し、その内部1つのスタックフレームを保存する(スタックフレーム)、Javaメソッド呼び出しに何度も対応します仮想マシンスタックはスレッドプライベートはい、ライフサイクルはスレッドと同じです。
仮想マシンスタックのスーパーバイザーJavaプログラムの操作、ローカル変数とメソッドの部分的な結果を保存し、メソッドの呼び出しと戻りに参加します。

ヒープとスタックの機能:
スタックはランタイムの単位であり、ヒープはストレージの単位です。
つまり、スタックはプログラムの実行中の問題、つまりプログラムの実行方法やデータの処理方法を解決します。ヒープは、データストレージの問題、つまり、データをどこにどのように配置するかを解決します。

仮想マシンスタックの長所と短所:
利点は、クロスプラットフォームであり、命令セットが小さく、コンパイラーが実装しやすいことです。欠点は、パフォーマンスが低下し、同じ関数でより多くの命令が必要になることです。

スタックフレームの概要:スタックフレーム
は、仮想マシンスタックの基本単位です。各メソッドはスタックフレームに対応します、そしてJVMの実行方法はスタックフレームを実行することですプッシュ操作、そして実行エンジンによって実行され、実行が完了した後、このスタックフレームが実行されますポップ操作

つまり、各メソッドはスタックフレームに対応し、メソッドの実行はスタックフレームをプッシュし、実行の終了はスタックフレームをポップします。

仮想マシンスタックの実行プロセス:
JVMは、Javaスタック上で2つの直接操作、つまりスタックフレームのみを実行します。プッシュアンドポップ先入れ先出し "/"先入れ先出し「原則。
つまり、JVMがメソッドを実行すると、このメソッドに対応するスタックフレームは、最初に仮想マシンスタックにプッシュされます、ピリオドでコードを順番に実行し、メソッド==で他のメソッドが呼び出されると、呼び出されたメソッドに対応するスタックフレームが仮想マシンスタック==にプッシュされます。最終的に仮想マシンスタックにプッシュするメソッドの実行を率先して行う、トップレベルのメソッドが実行されると、スタックがポップされ、次のレベルのメソッドの実行が続行されます。
トップレベルのメソッドは「現在のスタックフレーム"、対応するメソッドは"現在の方法"、メソッドを定義するクラスは、"現在のクラス"!
実用的なデモ:

public class TestStack {
    
    

    public static void main(String[] args) {
    
    
        System.out.println("main方法开始执行");
        method1();
        System.out.println("main方法执行结束");
    }

    public static void method1()
    {
    
    
        System.out.println("method1方法开始执行");
        method2();
        System.out.println("method1方法执行结束");
    }

    public static void method2()
    {
    
    
        System.out.println("method2方法开始执行");
        method3();
        System.out.println("method2方法执行结束");
    }

    public static void method3()
    {
    
    
        System.out.println("method3方法开始执行");
        System.out.println("method3方法执行结束");
    }
}

結果:

の結果

この
デバッグ結果
時点で、プログラムデバッグします。この時点で、4つのメソッドを定義します。各メソッドはスタックフレームに対応し、メインメソッドが実行を開始すると、メインメソッドに対応するスタックフレームがスタックにプッシュされ、method1が呼び出されます。メソッド。仮想マシンはmethod2メソッドをスタックにプッシュし、このときメインメソッドは実行されないため、メインメソッドはスタックからポップされず、method1メソッドの後にソートされます。このとき、method1は現在のスタックフレームです。 、プログラムは引き続きmethod1のコードを実行し、method1はmethod2 ...などを呼び出します。method3が実行されると、仮想マシンはmethod3をスタックにプッシュし、method1、method2、およびmainメソッドは実行されないため、これは仮想マシンスタックには4つのスタックフレームがあります。method3が実行されると、method3がポップアウトします。現時点では、仮想マシンスタックに残っているスタックフレームは3つだけで、method2は現在のスタックフレームです...同様に、method1メソッドを実行すると、mainメソッドのスタックフレームのみが仮想マシンスタックに残り、実行エンジンはmainメソッドの残りのコードを順番に実行します。
詳細な説明
注意:

  • 異なるスレッドに含まれるスタックフレームは相互に参照できません。つまり
    、1つのスタックフレーム内別のスレッドのスタックフレームを参照することはできません
  • 現在のメソッドが他のメソッドを呼び出す場合、メソッドが戻ると、現在のスタックフレームはこのメソッドの実行結果を
    前のスタックフレームに返します。その後、仮想マシンは現在のスタックフレームを破棄し、前のスタックフレームが現在のスタックフレームになります。
    Javaメソッドには、関数を返す方法が2つあります。1つは通常の関数戻りであり、return命令を使用する方法と、もう1つは
    例外をスローする方法です。どの方法を使用しても、スタックフレームがポップされます。

スタックサイズと例外処理を設定します
。Java仮想マシンの仕様では、Javaスタックのサイズを動的または固定にすることができます。固定サイズのJava仮想マシンスタックを使用する場合、各スレッドのJava仮想マシンスタック容量は、スレッドの作成時に個別に選択できます。スレッド要求によって割り当てられたスタック容量がJava仮想マシンスタックで許可されている最大容量を超えると、Java仮想マシンはStackOverFlowError例外をスローします。
Java仮想マシンスタックを動的に拡張でき、拡張しようとしたときに十分なメモリを適用できない場合、または新しいスレッドを作成するときに対応する仮想マシンスタックを作成するのに十分なメモリがない場合、Java仮想マシンはOutOfMemoryErrorは異常です。

つまり
、すべてのスタックフレームにはサイズがあるため、スタックフレームのサイズはメソッドコードに関連しています。
スタックサイズが変更されない場合、同時に実行されるメソッドが多すぎると、仮想マシンのスタックスペースがスタックフレームで埋められます。この時点で、メソッドの呼び出しを続行し、スタックフレームが追加されると、スタックがオーバーフローし、StackOverFlowErrorが発生します。
スタックのサイズが動的に変化する場合、現時点でスタックがいっぱいになると、JVMは自動的にスタックのスペースを拡張しますが、この時点でメモリが不足していると、メモリオーバーフローが発生し、OutOfMemoryErrorが報告されます。

例:
スタックサイズが固定されている場合、StackOverFlowErrorを発生させる最も簡単な方法は、メソッドを再帰的に呼び出すことです。呼び出しごとに新しいスタックフレームがスタックにプッシュされます。再帰が多すぎてスタックフレームが仮想マシンスタックでいっぱいになると、例外がスローされます。

	public class TestStack {
    
    
    public static int i=0;
    public static void main(String[] args) {
    
    
        System.out.println(i++);
       main(args);
    }
}

出力結果:
出力結果
この時点で、間違ったロジックが原因でプログラムが再帰し、スタックフレームが仮想マシンスタックで一杯になり、最終的に、9777番目のスタックフレームがスタックにプッシュされると、仮想マシンスタックは新しいスタックフレームを収容できなくなります。例外を投げる!

仮想マシンスタックのサイズを設定します
。目の前の論理エラーが原因で例外が発生したことがわかります。プログラムの実行中に多数のメソッドを同時に実行する必要がある場合は、例外を回避するために仮想マシンスタックのサイズを設定する必要があります。

仮想マシンのスタックサイズの指示を設定します。

-Xss *k

例:上記のプログラムに異なる仮想マシンスタック
スタックのサイズを設定する
結果
を設定するこの時点で、仮想マシンスタックを108k(最小要件)に設定し、同時に実行できるメソッドは978のみであることがわかりました。

仮想マシンスタックのサイズを設定する
結果

仮想マシンのスタックサイズを4096kに設定すると、より多くのメソッドを実行できます。

おすすめ

転載: blog.csdn.net/qq_42628989/article/details/104360779