JVMガベージコレクターとメモリ割り当て戦略1(JVMヒープ、スタック、メソッド領域のメモリオーバーフローの場合)


序文

JavaとC ++は、動的メモリ割り当てとガベージコレクションテクノロジーに囲まれた高い壁を直接持っています。壁の外側の人々は入りたがり、壁の内側の人々は入りたがります。Javaエンジニアとして、秘密を探ります。壁の..
この記事は、JVMガベージコレクタとメモリ割り当て戦略モジュールの学習の最初の記事であり、主に実際の学習の前の舗装として使用されます。主に、JVMランタイムデータ領域と、各領域でメモリオーバーフローが発生するシナリオを紹介します。以下では、実際のコードの競合を通じて、JVMのさまざまな領域のメモリオーバーフローシナリオをシミュレートおよび分析します。


JVMランタイムデータ領域

ここに画像の説明を挿入

プログラムカウンター

プログラムカウンタは、現在のスレッドによって実行されている仮想マシンのバイトコード命令のアドレスを記録するために使用される小さなメモリ領域です。マルチスレッドの場合、CPUリソースを取得した後、現在のスレッドが最後に実行されたコード位置にすばやく回復するには、各スレッドに独自の個別のプログラムカウンターが必要であり、各スレッド間のプログラムカウンターは相互に影響しません。したがって、プログラムカウンタはスレッド分離データ領域に属します。この領域は、「Java仮想マシン仕様」でOutOfMemoryErrorが言及されていない唯一の場所でもあります。

仮想マシンスタック

仮想マシンスタックもスレッド専用です。各スレッドには仮想マシンスタックがあります。ライフサイクルはスレッドのライフサイクルと同じです。現在のスレッドがメソッドを呼び出さない場合は、スタックフレームが作成されて格納されます。ローカル変数テーブル、オペランドスタック、動的リンク、およびメソッド。エクスポートおよびその他の情報の場合、各メソッドが呼び出されて実行されてから実行が完了するまでのプロセスは、仮想マシンスタック内のこのスタックフレームをにプッシュするプロセスに対応します。スタックから飛び出します。
「Java仮想マシン仕様」では、この領域に対して2つの例外が定義されています。1。スレッドによって要求されたスタックの深さが仮想マシンで許可されている最大の深さよりも大きい場合、
StackOverflowError例外がスローされます; 2スタックダイナミクスのサポート拡張された仮想マシンで、自動スタック拡張中に十分なメモリを適用できない場合、OutOfMemoryErrorがスローされます(現在の仮想マシンが動的スタック拡張をサポートしていない場合、StackOverflowErrorは次の場合にスローされます。スタック内の残りのスペースは、新しいスタックフレームを作成できません)

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

ネイティブメソッドスタックは仮想マシンスタックに似ており、仮想マシンスタックはJavaメソッド用であり、ローカルメソッドスタックはネイティブメソッド用です。

ヒープ

ヒープは、仮想マシンによって管理される最大のメモリです。スレッドによって共有されるメモリ領域です。仮想マシンの実行中に作成されたオブジェクトインスタンスの格納を仮想マシンが開始したときに作成されます。また、キー領域でもあります。ガベージコレクションとメモリフラグメンテーション用。上図に記載されている新世代、旧世代、エデンエリアなどは、「Java仮想マシン仕様」のヒープの詳細な分割ではなく、ガベージの世代設計に基づくヒープの論理的な分割です。ガベージコレクターの設計の観点から、上図のヒープの詳細な分割については議論の余地があります。このセクションでは、さまざまなガベージコレクターについて後で詳しく分析します。メモリ割り当ての観点から、すべてのスレッドで共有されるヒープを複数のスレッドプライベート割り当てバッファ(TLAB)に分割して、オブジェクト割り当ての効率を向上させることもできます。javaヒープを分割する目的は、メモリの回復とメモリの割り当てを改善することです。もっと早く。

メソッドエリア

ヒープと同様に、メソッド領域もスレッドによって共有されるメモリ領域であり、主にタイプ情報、定数、JVMによってロードされる静的変数などのデータを格納するために使用されます。メソッド領域と言えば、「永続生成」の概念に言及する必要があります。JDK8より前は、永続生成はHotSpot仮想マシンでメソッド領域を実装するために使用されていました。つまり、メソッド領域はヒープに配置されるため、節約できます。メソッド領域に個別のガベージコレクションとメモリ管理を記述する必要があります。ただし、この設計により、仮想マシンはメモリオーバーフローを起こしやすくなります。参照パーマネントコードの上限は、構成されていない場合でも-XX:MaxPerSizeです。デフォルトの上限があり、定数プールがますます一定になるにつれて、ロードされる情報の種類が増えます。また、アンロードとリサイクルの条件の種類が厳しくなるため、メモリオーバーフローが発生する可能性が高くなります。 。JDK8以降は、ローカルメモリのメタスペースを使用してメソッド領域を実装するため、オペレーティングシステムメモリの上限に触れない限り、メソッド領域を動的に無限に拡張できます。「Java仮想マシン仕様」では、メソッド領域がメモリ割り当て要件を満たせない場合にOutOfMemoryError例外がスローされることが規定されています。

ダイレクトメモリ

厳密に言えば、ダイレクトメモリはjvmランタイムデータ領域の一部ではなく、「Java仮想マシン仕様」で定義されているメモリ領域でもありません。ただし、メモリ領域のこの部分も頻繁に使用され、OutOfMemoryErrorが表示される可能性もあります。この領域は主にソケットを格納するために使用されます。Javaプログラムがデータベースなどの他のサービスと通信する必要がある場合、情報を送受信するためのソケットを作成する必要があります。ソケットはオペレーティングシステムレベルであり、 jvmはJavaプログラムの接続作成命令を受け取ります。次に、オペレーティングシステムの関数が呼び出されてオフヒープメモリが直接割り当てられ、Javaヒープに格納されているDirectByteBufferオブジェクトがこのメモリへの参照として使用されて動作します。直接メモリは仮想マシンによって制限されませんが、メソッド領域と同様にオペレーティングシステムのメモリの上限によって制限され、メモリが割り当てを満たさない場合にもOutOfMemoryErrorをスローします。

仮想マシンスタックOutOfMemoryError、StackOverflowError

スタック上のこれら2つの例外に関して、どのような状況でどのような種類の例外が発生するかを明確にする必要があります。

  1. スタックは動的拡張をサポートしておらず、スレッドスタック要求スタックの深さが仮想マシンスタックの最大深さを超えていますStackOverflowError
  2. スタックは動的拡張をサポートします(マルチスレッドシナリオとシングルスレッドシナリオでは、OutOfMemoryErrorの処理アイデアが異なります)OutOfMemoryError
    PS:スタックは仮想マシン自体の動的拡張をサポートします。この検証は、最大値を制限するサーバーで実行するのが最適です。 32G Windowsオペレーティングシステムなどの単一プロセスのメモリ。それ以外の場合、スタックが動的拡張をサポートしている場合、スタックのサイズはオペレーティングシステムによって制限されます。私のコンピューターは、16Gが結果を生成するまで長時間待たなければなりません。 。主に、動的拡張をサポートするClassicやその他の仮想マシンをダウンロードする必要はありません。もちろん、2番目のシナリオには調整方法があります。シングルスレッドシナリオでOutOfMemoryErrorが発生した場合、スタックの最大深度が、内圧がに絞られたときにスレッドが必要とする深度を満たしていないことを意味します。スタックの深さを増やすには、ヒープメモリを減らすか、単一のスタックフレームのサイズを減らすことで、スタックの深さを増やすことができます。マルチスレッドシナリオの場合は、新しいスレッドのスタックを作成できないために発生するメモリとOOMの上限この時点で、より多くのスレッドと引き換えに、ヒープメモリを減らすか、単一スタックの深さを減らすことができます。
    次に、最初のシナリオスタックが動的拡張をサポートしておらず、スレッドスタック要求スタックの深さが仮想マシンスタックの最大深さを超えていることを確認します。その結果、StackOverflowErrorが発生します。
public class StackSpace {
    
    
    int i=0;
    public void demo(){
    
    
        System.out.println(i);
        i++;
        demo();
    }

    public static void main(String[] args) {
    
    
        StackSpace stackSpace=new StackSpace();
        stackSpace.demo();
    }

}

結果の印刷:

0
...
9866
9867
9868
9869
9870
9871
Exception in thread "main" java.lang.StackOverflowError
	at java.base/java.nio.Buffer.<init>(Buffer.java:222)
	at java.base/java.nio.CharBuffer.<init>(CharBuffer.java:281)
	at java.base/java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:75)
	at java.base/java.nio.CharBuffer.wrap(CharBuffer.java:393)
	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:280)
	at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:211)
	at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120)
	at java.base/java.io.PrintStream.write(PrintStream.java:605)
	at java.base/java.io.PrintStream.print(PrintStream.java:676)
	at java.base/java.io.PrintStream.println(PrintStream.java:812)
	at com.oom.StackSpace.demo(StackSpace.java:9)
	at com.oom.StackSpace.demo(StackSpace.java:11)

堆OutOfMemoryError

ヒープメモリのオーバーフローを高速化するには、JVMのいくつかのパラメータを構成してヒープメモリを小さくする必要があります。

**
 * -Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
 * HeapSpace outOfMemoryError JVM堆内存溢出Demo
 * JVM参数:
 * -Xmx20m 最大堆内存
 * -Xms20m 最小堆内存
 * -Xmn10m 新生代内存
 * -XX:SurvivorRatio=8 新生代中Eden区与Survivor区比值
 * -XX:+HeapDumpOnOutOfMemoryError 堆内存溢出快照
 * -XX:+PrintGCDetails GC日志打印
 */
public class HeapSpace {
    
    
    static class Demo{
    
    

    }

    public static void main(String[] args) {
    
    

        List<Demo> demos=new ArrayList<>();

        while (true){
    
    
            demos.add(new Demo());

        }
    }

}

結果の印刷:

[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->5121K(19456K), 0.0090997 secs] [Times: user=0.03 sys=0.01, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13313K->19432K(19456K), 0.0120218 secs] [Times: user=0.06 sys=0.00, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->9836K(10240K)] 19432K->9836K(19456K), [Metaspace: 3190K->3190K(1056768K)], 0.1015416 secs] [Times: user=0.38 sys=0.01, real=0.10 secs] 
[Full GC (Ergonomics) [PSYoungGen: 7742K->8020K(9216K)] [ParOldGen: 9836K->7725K(10240K)] 17578K->15746K(19456K), [Metaspace: 3195K->3195K(1056768K)], 0.1425264 secs] [Times: user=0.77 sys=0.01, real=0.14 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 8020K->8006K(9216K)] [ParOldGen: 7725K->7725K(10240K)] 15746K->15731K(19456K), [Metaspace: 3195K->3195K(1056768K)], 0.1086842 secs] [Times: user=0.65 sys=0.00, real=0.11 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid7963.hprof ...
Heap dump file created [27802776 bytes in 0.091 secs]
Heap
 PSYoungGen      total 9216K, used 8192K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 100% used [0x00000007bf600000,0x00000007bfe00000,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  to   space 1024K, 46% used [0x00000007bff00000,0x00000007bff76e30,0x00000007c0000000)
 ParOldGen       total 10240K, used 7725K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 75% used [0x00000007bec00000,0x00000007bf38b770,0x00000007bf600000)
 Metaspace       used 3227K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:267)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
	at java.util.ArrayList.add(ArrayList.java:464)
	at com.oom.HeapSpace.main(HeapSpace.java:27)

メソッド領域OutOfMemoryError

メソッド領域は、JDK6-JDK7-JDK8のアップグレードプロセス中に大幅に変更された領域です。メタスペースを削除するための永続的な生成の出現により、ランタイム定数かどうかなど、メソッド領域について多くの論争がありました。プールはヒープ内またはヒープ内にあります。メタスペース内ですか?ストリングインターン方式による一連の面接の質問もありますか?この2点は上司の記事を参考にできるので、もっといい記事を2つ選びました。

  • ランタイム定数プールはヒープまたはメタスペースにありますか?https://blog.csdn.net/weixin_44556968/article/details/109468386
  • ストリングインターン方式による一連の面接の質問?https://blog.csdn.net/qq_36426468/article/details/110150453

ダイレクトメモリOutOfMemoryError

ここに画像の説明を挿入

/**
 * -XX:MaxDirectMemorySize=10m 指定直接内存大小
 */
public class DirectMemory {
    
    
    private static final int _1mb=1024*1024;


    public static void main(String[] args)throws Exception {
    
    

        Field declaredField = Unsafe.class.getDeclaredFields()[0];
        //设置允许暴力访问
        declaredField.setAccessible(true);
        // 传入启动类加载器(启动类加载器是c++实现的,在java代码中为null)
        Unsafe unsafe = (Unsafe) declaredField.get(null);
        while (true){
    
    
            unsafe.allocateMemory(_1mb);
        }
    }
}

ダイレクトメモリによって引き起こされるメモリオーバーフローには、HeapDumpファイルに明らかな異常がないという明らかな機能があります。メモリオーバーフロー後に生成されたダンプファイルが小さく、プログラムが直接または間接的にDirectMemory(通常は間接)を使用していることをリーダーが検出した場合使用はNIO)、ダイレクトメモリの理由に焦点を当てることができます。

おすすめ

転載: blog.csdn.net/yangxiaofei_java/article/details/115272721