インタビュー:Javaに習熟している;インタビュアー:JVM仮想マシンのメモリモデルの最下位の原則について話しましょう。詳細に説明し、その理由を理解する必要があります。それを読んだ後、あなたはあなたの履歴書にJavaに堪能であることをあえて書きますか?

Javaに習熟していますか?次の底层中的底层原理ことを知っているかどうか見てみましょう。JVMに関しては、そのメモリモデルについて話す必要があります。JVMの仕様によれば、
JVMメモリは5つの部分に分けられます虚拟机栈VM stack、、、、、および。下の図に示すように、これら5つの領域の原理を詳細に説明します。(読者の時間を節約し、すべての人の理解と記憶を促進するために、著者はすべての知識ポイントをレイヤーとセグメントに分割し、短い言語を使用して説明、簡潔、包括的であり、すべての文が重要なポイントです。)堆heap方法区Method Area程序计数器Program Counter Register本地方法栈Native Method Stack

ここに画像の説明を挿入

1.仮想マシンスタック(VMスタック)

  • 各スレッドには、スレッドの作成時に作成されるプライベートスタックがあります。

  • StackOverflowErrorおよびOutOfMemoryError例外をスローできます。

    • スレッドによって要求されたスタックサイズが仮想マシンスタックで許可されている最大サイズを超えると、Java仮想マシンはスタックオーバーフローエラー例外をスローします。
    • 仮想マシンスタックを動的に拡張でき、拡張しようとしたときに十分なメモリを適用できない場合、または新しいスレッドを作成するときに、対応する仮想マシンスタックを作成するのに十分なメモリがない場合、Java仮想マシンはOutOfMemoryErrorをスローします例外。
  • メソッド呼び出し関連の知識:

    • メソッドが呼び出されると、スタックフレームが作成され、仮想マシンスタックにプッシュされます。メソッドが実行された後、スタックフレームはスタックからポップされ、破棄されます。
    • スタックに格納されるのは「スタックフレーム」と呼ばれるもので、各メソッドがスタックフレームを作成します。スタックフレーム構造は、ローカル変数テーブル(基本データ型とオブジェクト参照)、オペランドスタック、メソッド出口などに分けられます。情報。
    • デバッグすると、以下に示すように、フレームがはっきりとわかります
      ここに画像の説明を挿入
      。対応する回路図は次のとおりです。
      ここに画像の説明を挿入
  • メソッドのすべてのローカル変数がここに格納されるため、スタックメモリは仮想マシンスタックのスタックフレーム内のローカル変数テーブルを参照します。

  • スタックのサイズは、固定または動的に拡張できます。スタック呼び出しの深さがJVMで許可されている範囲よりも大きい場合、StackOverflowErrorエラーがスローされますが、この深さの範囲は固定値ではありません(vmoptionsファイルのパラメーターを変更してサイズを調整することもできます。詳細については、この記事の最後にある付録です。ここでは拡張しません)。次のコードでテストできます。

public class HeapDeepDemo {
    
    

    private static int index = 0;

    public void addIndex() {
    
    
        index++;
        addIndex();
    }


    public static void main(String[] args) {
    
    

        HeapDeepDemo heapDeepDemo = new HeapDeepDemo();

        try {
    
    
            heapDeepDemo.addIndex();
        } catch (Error e) {
    
    
            System.out.println("Stack deep : " + index);
            e.printStackTrace();
        }
    }
}

4つの実行の結果は、次のように異なります。
ここに画像の説明を挿入ここに画像の説明を挿入
ここに画像の説明を挿入

ここに画像の説明を挿入


2.ネイティブメソッドスタック

Javaメソッドからネイティブメソッドスタックを呼び出すプロセスは次のとおり
ここに画像の説明を挿入
です。ネイティブメソッドスタックとは何ですか。

  • ネイティブメソッドは、Javaが非Javaコードを呼び出すためのインターフェースです。メソッドの実装は、CやC++などの非Java言語で実装されます。彼の具体的なアプローチは、ネイティブメソッドをネイティブメソッドスタックに登録し、実行エンジンの実行時にネイティブメソッドライブラリをロードすることです。

なぜネイティブメソッドスタックを使用するのですか?

  • 一部のタスクをJavaで実装するのは簡単ではありません。また、プログラムの効率が要求される場合や、JavaアプリケーションがJavaの外部の環境と対話する必要がある場合もあります。これが、ネイティブメソッドが存在する主な理由です。

ローカルメソッドスタックの知識ポイント:

  • 上記でVM仮想マシンスタックについて説明しました。仮想マシンスタックはJavaメソッドの呼び出しを管理するために使用され、ローカルメソッドスタックはローカルメソッドの呼び出しを管理するために使用され、それぞれが独自の役割を果たします。
  • 仮想マシンスタックVMスタックと同様に、ネイティブメソッドスタックもスレッドプライベートです。
  • 仮想マシンスタックVMスタックと同様に、固定または動的にスケーラブルなサイズで実装できます。
  • VMスタックと同様に、ネイティブメソッドスタックもStackOverflowErrorおよびOutOfMemoryError例外をスローできます。
    • スレッドによって要求されたスタックサイズがネイティブメソッドスタックで許可されている最大サイズを超えると、Java仮想マシンはスタックオーバーフローエラー例外をスローします。
    • ネイティブメソッドスタックを動的に拡張でき、拡張しようとしたときに十分なメモリを適用できない場合、または新しいスレッドを作成するときに、対応するネイティブメソッドスタックを作成するのに十分なメモリがない場合、Java仮想マシンはOutOfMemoryErrorをスローします例外。

3.プログラムカウンターレジスタ

3.1X86アーキテクチャのIP命令ポインタレジスタとの類似性

プログラムカウンターはPC寄存器、アセンブリとマイクロコンピューターの原理で、X86アーキテクチャーのCPUのIP命令ポインターレジスターに類似したものとして変換することもできます。詳細については、他のブログ(https://blog.csdn.net )を参照してください。 / MrYushiwen / article / details/122627634のおおよその内容は
次のとおりです。
ここに画像の説明を挿入

x86アーキテクチャの登録図は次のとおりです:(私たちの大学の教科書の写真もこのように描かれていることを忘れないでください。写真は永遠であり、1つは永遠に回覧されます!ハハハ)
ここに画像の説明を挿入

  • Java1.2以降のx86アーキテクチャでip命令ポインタレジスタを比較するのはなぜですか。LinuxのJVMはpthreadに基づいて実装されています。Javaスレッドはオペレーティングシステムによって実装されており、Javaスレッドは常に必要であると直接言えます。 1つのフォームはOSスレッドにマッピングされます。マッピングモデルは、1:1(ネイティブスレッドモデル)、n:1(グリーンスレッド/ユーザーモードスレッドモデル)、m:n(ハイブリッドモデル)になります。 。
  • 現在、ほとんどのプラットフォームで1:1モデルを使用しています。つまり、各Javaスレッドは実行のためにOSスレッドに直接マップされます。この時点で、ネイティブメソッドはネイティブプラットフォームによって直接実行され、抽象JVMレベルでの「pcレジスタ」の概念(ネイティブCPU上の実際のPCレジスタ)に注意を払う必要はありません。現在のJavaのスレッドの本質は、実際にはオペレーティングシステムのスレッドです。

3.2JVMのプログラムカウンター

  • これはJVMの小さなメモリスペースです。JVMは同時に実行される複数のスレッドをサポートし、各スレッドには独自のプログラムカウンターがあります。
  • その役割は、現在のスレッドによって実行されるバイトコードの行番号インジケーターとして見ることができます。仮想マシンの概念モデルでは、バイトコードインタープリターが動作すると、このカウンターの値を変更して、実行する次のバイトコード命令を選択します。分岐、ループ、ジャンプ、例外処理、スレッド回復などの基本機能はすべて必要です。完了するには、このカウンターに依存してください。
  • ランタイム機能:
    • スレッドの作成から始めました。
    • スレッドがJavaメソッドを実行している場合、このカウンターは実行されている仮想マシンのバイトコード命令のアドレスを記録します。
    • ネイティブメソッドが実行されている場合、この手法の値は空です(未定義)。
    • このメモリ領域は、Java仮想マシン仕様でOutOfMemoryError条件を指定しない唯一の領域です。

4.メソッドエリア

  • メソッド領域はすべてのスレッドで共有されます。
  • これは、クラス情報、定数、静的変数、仮想マシンによってロードされたジャストインタイムコンパイラによってコンパイルされたコードなどのデータを格納するために使用されます。
    ここに画像の説明を挿入
  • メソッド領域は論理的にはヒープの一部ですが、ヒープと区別するために、通常は「非ヒープ」と呼ばれます。
  • 永続世代(PermGen)、メソッド領域(メソッド領域)、およびメタスペース(メタスペース)の間の関係:
    • メソッドエリアは仕様レベルのものであり、このエリアに何を格納するかを指定します。PermGenとMetaspaceは、メソッドエリアの異なる実装です。
    • 永続的な生成(PermGen)は、Java7および以前のJVMのメソッド領域(メソッド領域)の実装です。
    • Metaspace(Metaspace)は、メソッド領域(メソッド領域)用のJava8以降のJVMの実装です。
    • たとえば、メソッド領域を携帯電話と比較した後、不死帯をNokiaの携帯電話と比較し、メタスペースをHuaweiの携帯電話と比較することができます。
  • Java 8で永続世代(PermGen)をメタスペースに置き換える理由:
    • 永続世代のサイズには制限があります。ロードされるクラスが多すぎると、永続世代でメモリオーバーフローが発生する可能性があります。つまり、java.lang.OutOfMemoryError:PermGenです。したがって、JVMの開発者はこのメモリをより柔軟に管理できるため、頻繁に表示されることはありません。このようなOOM。
    • JRockitには永続的な世代がないため、永続的な世代を削除すると、HotSpotJVMとJRockitVMの統合が容易になります。
    • Java 7では、インターンされた文字列はヒープの永続世代に割り当てられなくなりましたが、ヒープの主要部分である若い世代と古い世代に割り当てられています。Java8では、公式文書で永続世代の削除について言及されていましたが、インターン文字列に関連するその他の変更については言及されていませんでした。したがって、文字列定数プールはJava8のヒープに格納されていると判断できます。
    • つまり、Java8では、メソッド領域が元の永続的な生成から、メタスペース(クラス情報)とヒープ実装(定数プール、静的変数)の2つの部分に変更されました。ヒープには通常のオブジェクトと定数プールが含まれています。新しいString()がヒープに配置されます。String:: internメソッドは、最初にヒープ内の定数プールからそれを取得します。定数プールに定数プールがない場合、値は文字列の値は定数プールに保存され、その参照を返します。次にString :: internメソッドが呼び出されると、定数プールの値が直接返されます。
    • また、永続的な生成(PermGen)とメタスペース(Metaspace)は、前述したメソッドエリア(メソッドエリア)の異なる実装であるため、定数プールはJava8のメソッドエリアにあるとも言えます。
    • メタスペースはネイティブメモリを使用して実装されます。つまり、そのメモリは仮想マシンにないため、理論的には、物理​​マシンは、メモリのJVM自体に制限されることなく複数のメモリ割り当てを持つことができます。
    • メタスペースのスペース占有率が設定された最大値に達すると、GCがトリガーされ、デッドオブジェクトとクラスローダーが収集されます。

5.ヒープ

Java7および以前の構造図:
ここに画像の説明を挿入
第4章メソッド領域(メソッド領域)で、Java8について言及した場合、永続世代(PermGen)がメタスペース(メタスペース)に置き換えられたため、Java8以降の図は次のようになります。
ここに画像の説明を挿入

  • ヒープが世代を超えている理由:

    • 生成の唯一の理由は、GCのパフォーマンスを最適化することです。
    • 生成がない場合、すべてのオブジェクトが1つのブロックに含まれ、GCで検出されたオブジェクトを見つけることは無意味であるため、ヒープのすべての領域がスキャンされます。そして、私たちのオブジェクトの多くは死にかけています。たとえば、若い世代のオブジェクトは基本的に死んでいます(80%以上)ので、若い世代のガベージコレクションアルゴリズムは複製アルゴリズムを使用します(将来的にブログ投稿を作成します) )詳細)。
    • 世代別の場合は、新しく作成したオブジェクトを特定の場所に配置し、最初に「デッド」オブジェクトが保存されている領域をGC中にリサイクルします。これにより、多くのスペースが解放されます。
  • マイナーGC、メジャーGC、フルGCの違い:

    • マイナーGCまたはヤングGC。若い世代(エデンおよびサバイバーエリアを含む)のメモリスペースを再利用するために使用されます。
    • 古いGCは、古いメモリスペースをクリーンアップすることです。
    • フルGCは、若い世代と古い世代を含むヒープスペース全体を再利用することです。Java7以前では、永続世代も含まれています。Java8以降では、メタスペースが変更されるため、そのガベージコレクションはJavaによって制御されません。デフォルトでは、メタスペースのメモリスペースは、使用されるオペレーティングシステムのメモリスペースです。 、したがって、スペースメタスペースの容量は比較的十分であり、メタスペースのスペースが不足するという問題は発生しません。メタスペースのスペース占有が設定された最大値に達すると、GCもトリガーされてデッドオブジェクトを収集します。クラスローダー。
    • メジャーGC、一部の人はそれをオールドGCと同一視し、一部の人はそれをフルGCと同一視します。このメジャーGCについては言及しないようにしています。言及されている場合は、それがオールドGCかフルGCGCかを相手に尋ねてください。
    • GCおよびGC回復アルゴリズムに関して、著者は将来、ブログ投稿の詳細な紹介を書きます。
  • HotSpot JVMは、若い世代を3つの部分に分割します。

    • 3つの部分は、1つのエデンエリアと2つのサバイバーエリアです(それぞれfromとtoと呼ばれます)。デフォルトの比率は8:1:1です
    • サバイバーがいない場合、エデンエリアでマイナーGCが実行されるたびに、サバイバーオブジェクトが古い世代に送信されます。古い世代はすぐにいっぱいになります。古い世代のメモリスペースが特定のしきい値を超えるか、新しい世代よりもはるかに大きい場合、フルGCが実行され、フルGCはマイナーGCよりもはるかに長い時間を消費します。
    • 2つのサバイバーエリアを設定する最大の利点は、断片化を解決することです。
  • 若い世代がどのようにして古い世代になるか

    • 通常の状況では、新しく作成されたオブジェクトはエデンエリアに割り当てられます(一部の大きなオブジェクトは特別に処理されます)。最初のマイナーGCの後、
      これらのオブジェクトがまだ生きている場合、それらはサバイバーエリアに移動されます。サバイバーエリアのマイナーGCでオブジェクトが生き残るたびに、年齢が1年
      ずつ増加します。年齢が一定のレベル(通常は16倍)に増加すると、古い世代に移動します。

6.付録(VMオプションパラメータ)

ここに画像の説明を挿入
開封後の内容は以下の通りです。
ここに画像の説明を挿入

ファイルパスは次のとおりです:
ここに画像の説明を挿入
ファイルの内容は次のとおりです:
ここに画像の説明を挿入
パラメータ値は次のとおりです:

  • -Xms1024m、JVM初期ヒープメモリを1024mに設定します。この値は-Xmxと同じように設定して、各ガベージコレクションが完了した後のJVMによるメモリの再割り当てを回避できます。
  • -Xmx2048m、JVMの最大ヒープメモリを2048mに設定します。
  • -Xss512k、各スレッドのスタックサイズを設定します。JDK5.0以降、各スレッドのスタックサイズは1Mで、以前は各スレッドスタックのサイズは256Kでした。同じ物理メモリの下で、この値を減らすと、より多くのスレッドを生成できます。もちろん、オペレーティングシステムには、プロセス内のスレッド数に制限があり、無期限に生成することはできません。スレッドスタックのサイズは両刃の剣です。設定が小さすぎると、特にスレッドに再帰的で大きなループがある場合に、スタックオーバーフローが発生する可能性があります。作成されるスタックの数に影響します。マルチの場合、スレッド化されたアプリケーションでは、メモリオーバーフローエラーが発生します。
  • -Xmn341m、若い世代のサイズを341mに設定します。ヒープ全体のサイズを考えると、若い世代を増やすと古い世代が減り、逆もまた同様です。この値はJVMガベージコレクションに関連しており、システムパフォーマンスに大きな影響を与えます。公式の推奨事項は、ヒープサイズ全体の3/8として構成することです。
  • -XX:NewSize = 341m、若い世代の初期値を341Mに設定します。
  • -XX:MaxNewSize = 341m、若い世代の最大値を341Mに設定します。
  • -XX:PermSize = 512m、永続生成の初期値を512Mに設定しますが、java8以降ではサポートされていないため、代わりに-XX:MetaspaceSize=512mを使用してください。
  • -XX:MaxPermSize = 512m、永続生成の最大値を512Mに設定します。これはjava8以降でもサポートされていません。代わりに、-XX:MaxMetaspaceSize=512mを使用してください。
  • -XX:NewRatio = 2、古い世代に対する若い世代(1つのエデンと2つのサバイバーエリアを含む)の比率を設定します。若い世代と古い世代の比率が1:2であることを示します。
  • -XX:SurvivorRatio = 8、若い世代のサバイバーエリアに対するエデンエリアの比率を設定します。2つのサバイバーエリア(デフォルトでは、若い世代のJVMヒープメモリに同じサイズの2つのサバイバーエリアがあります)と1つのエデンエリアの比率が1:1:8であることを示します。つまり、1つのサバイバーエリアが1/10を占めます。若い世代全体のサイズの。
  • -XX:MaxTenuringThreshold = 15。詳細については、JVMシリーズのメモリ割り当ておよびリカバリ戦略におけるオブジェクトのエージングプロセスを参照してください。
  • -XX:ReservedCodeCacheSize = 240mは、コンパイルされたメソッドによって生成されたネイティブコードを格納するために使用されるコードキャッシュのサイズを設定します。
  • パラメータ設定の詳細については、公式ドキュメントを参照してください:https ://www.oracle.com/java/technologies/javase/vmoptions-jsp.html

この記事は
、CSDNYuShiwenに書かれた2022.2.15に終了しました

おすすめ

転載: blog.csdn.net/MrYushiwen/article/details/122943314