JVM -- JVM メモリ構造: プログラム カウンタ、仮想マシン スタック、ローカル メソッド スタック、ヒープ、メソッド領域 (2)

読む前の参考

https://blog.csdn.net/MinggeQingchun/article/details/126947384

JVMのメモリ構造は、プログラムカウンタ、仮想マシンスタック、ローカルメソッドスタック、ヒープ、メソッド領域の5つに大別されます。これに加えて、ヒープによって参照される JVM の外部に直接メモリがあります。

JVM8 公式 Web サイトの文書アドレス

Java® 仮想マシン仕様

JVM8 公式 Web サイト オプション パラメータ設定ドキュメント

 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

 Java HotSpot VM オプション

JDK1.8公式サイトアドレス 

https://docs.oracle.com/javase/8/docs/index.html

JDK1.6公式サイトアドレス

https://docs.oracle.com/javase/6/docs/index.html

JDK=JRE+開発ツールセット(Javacコンパイルツールなど)
JRE=JVM+Java SE標準クラスライブラリ

1. プログラムカウンター Program Counter Register (レジスター)

機能: 次の jvm 命令の実行アドレスを記憶する

特徴:

(1)スレッド専用

(2) メモリオーバーフローが発生しない

プログラム カウンター (プログラム カウンター レジスタ) は、JVM 内の小さなメモリ領域であり、現在のスレッドによって実行される仮想マシン バイトコード命令のメモリ アドレスを格納します (現在のスレッドによって実行されるバイトコードの行番号インジケータと見なすことができます)。スレッド

JVM のマルチスレッド化は、スレッドを順番に切り替えて CPU 実行タイム スライスを割り当てることで実現され、CPU は常に 1 つのスレッドでしか命令を実行しませんスレッドの切り替え後に正しい実行位置を確実に復元できるようにするために、各スレッドには独立したプログラム カウンターが必要であり、スレッド間のプログラム カウンターは互いに影響を与えることなく独立して格納されます。

プログラムカウンタは仮想マシン内部で保持され、開発者が操作する必要がないため、Java仮想マシン仕様でOutOfMemoryErrorを指定していないのはこの領域だけです。 

0: getstatic #20                     // PrintStream out = System.out; 
3: astore_1                          // -- 
4: aload_1                           // out.println(1); 
5: iconst_1                          // -- 
6: invokevirtual #26                 // -- 
9: aload_1                           // out.println(2); 
10: iconst_2                         // -- 
11: invokevirtual #26                // -- 
14: aload_1                          // out.println(3); 
15: iconst_3                         // -- 
16: invokevirtual #26                // -- 
19: aload_1                          // out.println(4); 
20: iconst_4                         // -- 
21: invokevirtual #26                // -- 
24: aload_1                          // out.println(5); 
25: iconst_5                         // -- 
26: invokevirtual #26                // -- 
29: return

インタプリタは命令を機械語として解釈し、実行のために CPU に渡します。プログラム カウンタは次の命令のアドレス行番号を記録します。これにより、次回インタプリタがプログラム カウンタから命令を取得し、解釈して実行する

マルチスレッド環境では、2 つのスレッド間でコンテキスト スイッチが発生した場合、プログラム カウンターはスレッド内の命令の次の行のアドレス行番号を記録し、次に実行できるようにします。 

次に、仮想マシン スタック Java 仮想マシン スタック

各スレッドの実行に必要なメモリは、仮想マシン スタックと呼ばれます。

各スタックは、各メソッド呼び出しによって占有されるメモリに対応する複数のスタック フレーム (フレーム) で構成されます。

各スレッドは、現在実行中のメソッドに対応するアクティブなスタック フレームを 1 つだけ持つことができます。

 The virtual machine stack (Java Virtual Machine Stacks) is thread-isolated. スレッドが作成されるたびに、対応する Java スタックが作成されます。つまり、各スレッドには独自の独立した仮想マシン スタックがあります。

このスタックには複数のスタック フレームも含まれます.メソッドが呼び出されるたびに, スタック フレームが作成され, スタックにプッシュされます. スタック フレームには,ローカル変数テーブル, 操作スタック, 動的リンク, メソッド出口などの情報が格納されます.メソッドの呼び出しから最終的に結果を返すまでのプロセスは、スタック フレームのプッシュからポップまでのプロセスに対応します。

仮想マシンのスタックは後入れ先出しのデータ構造です. スレッドの実行中は,現在のスタック フレームと呼ばれるスタックの一番上にあるスタック フレームのみが有効です.このスタック フレームに関連付けられたメソッドが呼び出されます.アクティブなフレーム スタックは、常に仮想マシン スタックの最上位要素です。

ローカル変数テーブルには、コンパイル時に認識されるさまざまな基本データ型とオブジェクト参照型が格納されます。通常、「スタック メモリ」と呼ばれるものは、ローカル変数テーブルの一部を指します。

ローカル変数テーブルに必要なメモリ空間はコンパイル時に割り当てられます. メソッドに入るとき, メソッドがフレームに割り当てる必要のあるメモリ量は固定されており, ローカル変数テーブルのサイズは操作中に変更されません.

64 ビット long と double 型のデータは 2 つのローカル変数空間を占有し、他のデータ型は 1 つの空間しか占有しません

以下のように、メソッド呼び出しは 3 つのスタック フレームを生成します。 

  

スタックメモリはガベージコレクションを伴わない. スタックメモリの生成は, メソッド呼び出しごとに生成されるスタックフレームメモリであり, スタックフレームメモリはメソッドが呼び出されるたびにスタックからポップされ, 自動的に再利用される.ガベージ コレクション管理。 

1. スタック オーバーフロー エラー

1. 固定サイズの場合、JVM は各スレッドの仮想マシン スタックに一定のメモリ サイズ (-Xss パラメータ) を割り当てるため、仮想マシン スタックが保持できるスタック フレームの数が制限されます。スタック フレームは継続的にスタックにプッシュされます スタックをポップしないと、現在のスレッドの仮想マシン スタックのメモリ空間が最終的に使い果たされ、 例外StackOverflowErrorスタックメモリオーバーフロー)

2. 動的拡張の場合、仮想マシン スタック全体のメモリが使い果たされ、新しいメモリを適用できない場合、OutOfMemoryError 例外がスローさ れます。

1. メソッド自体を再帰的に呼び出す

次のコードを実行します

/**
 * 栈内存溢出 java.lang.StackOverflowError
 * -Xss256k
 */
public class Stack2StackOverflowError {
    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);
        }
    }

    private static void method1() {
        count++;
        method1();
    }
}

エラーは次のとおりです。

java.lang.StackOverflowError

VM オプション 

構成可能な構成パラメーター VM オプション (内部構成パラメーター)

のような: VM で 

-Xms512m -Xmx512m -XX:PermSize=64M -XX:MaxPermSize=256m

各項目はスペースで区切ります

パラメータ 説明

- Xms768m: JVM初期ヒープメモリを768mに設定。この値を -Xmx と同じに設定すると、各ガベージ コレクションの完了後に JVM がメモリを再割り当てするのを回避できます。

-Xmx768m: JVMの最大ヒープメモリを768mに設定。
-Xss128k: 各スレッドのスタックサイズを設定。JDK5.0以降、各スレッドスタックのサイズは1M、それ以前は各スレッドスタックのサイズは256Kです。アプリケーションのスレッドが必要とするメモリ サイズに応じて調整する必要があります。同じ物理メモリで、この値を減らすと、より多くのスレッドが生成される可能性があります。ただし、OS ではプロセス内のスレッド数に制限があり、無限に生成することはできず、経験値は 3000 ~ 5000 程度です。この値を大きな値 (たとえば > 2MB) に設定すると、システムのパフォーマンスが大幅に低下することに注意してください。
-Xmn2g: 若い世代のサイズを 2G に設定します。ヒープ メモリ全体のサイズが決まると、若い世代を増やすと古い世代が減り、その逆も同様です。この値は JVM ガベージ コレクションに関連しており、システム パフォーマンスに大きな影響を与えます. 公式の推奨構成は、ヒープ サイズ全体の 3/8 です.
-XX:NewSize=1024m: Young 世代の初期値を 1024M に設定します。
-XX:MaxNewSize=1024m: 若い世代の最大値を1024Mに設定します。
-XX:PermSize=256m: 永続世代の初期値を 256M に設定します。
-XX:MaxPermSize=256m: 永続世代の最大値を 256M に設定します。
-XX:NewRatio=4: 古い世代に対する若い世代 (1 つの Eden と 2 つの Survivor エリアを含む) の比率を設定します。若い世代と古い世代の比率が 1:4 であることを示します。
-XX:SurvivorRatio=4: Young 世代の Survivor 領域に対する Eden 領域の比率を設定します。2 つの Survivor 領域 (JVM ヒープ メモリの若い世代には、デフォルトで同じサイズの 2 つの Survivor 領域があります) と 1 つの Eden 領域の比率が 2:4 であることを示します。つまり、1 つの Survivor 領域が、若い世代全体の 1/6 を占めることを示します。サイズ。
-XX:MaxTenuringThreshold=7: オブジェクトが Survivor 領域 (レスキュー スペース) で 7 回移動し、ガベージ コレクションされていない場合、古い世代に入るということを示します。0 に設定すると、若い世代のオブジェクトが Survivor 領域を経由せずに古い世代に直接入るため、大量の常駐メモリを必要とするアプリケーションの場合、効率が向上します。この値を大きくすると、若い世代のオブジェクトが Survivor 領域に複数回コピーされるため、若い世代のオブジェクトの存続時間が長くなり、オブジェクトが若い世代でガベージ コレクションされる可能性が高くなります。生成し、フル GC の頻度を減らすことで、サービスの安定性をある程度向上させることができます。
標準パラメーター。すべての JVM はこれらのパラメーターの機能をサポートする必要があり、下位互換性があります。たとえば、
-client - JVM をクライアント モードを使用するように設定します。管理効率は高くなく、通常はクライアント アプリケーションまたは開発およびデバッグに使用されます。このモードは、32 ビット環境で Java プログラムを直接実行する場合、デフォルトで有効になります。
-server - JVM をサーバー モードに設定します。このモードは、起動速度が遅くなりますが、ランタイム パフォーマンスとメモリ管理効率が高く、運用環境に適しています。このモードは、64 ビット対応の JDK 環境ではデフォルトで有効になっています。
非標準パラメーター (-X)、デフォルトの JVM はこれらのパラメーターの機能を実装しますが、すべての JVM 実装で満たされることは保証されておらず、下位互換性も保証されません。不安定なパラメーター (-XX)、そのようなパラメーターの各 JVM
実装異なる、将来サポートされない可能性がある、注意して使用する必要がある

2. CPU 使用率が高すぎる 

最初に Java ファイルを実行して .class ファイルを取得し、それを VM 仮想マシンにアップロードします。

/**
 * cpu 占用过高
 */
public class Stack3CPUFull {
    public static void main(String[] args) {
        new Thread(null, () -> {
            System.out.println("1...");
            while(true) {

            }
        }, "thread1").start();


        new Thread(null, () -> {
            System.out.println("2...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2").start();

        new Thread(null, () -> {
            System.out.println("3...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread3").start();
    }
}
# 后台运行java程序
nohup java Stack3CPUFull &

# top命令查看CPU使用情况;定位哪个进程对cpu的占用过高
top 

# ps命令进一步定位是哪个线程引起的cpu占用过高
ps H -eo pid,tid,%cpu | grep 进程id

# 根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
jstack 进程id

スレッド ID 32665 を 16 進数の 7F99 に変換して、CPU を占有しすぎているスレッドと、実行コード エラーが存在する行数を特定します。

3. スレッドの私的ロック (長期間プログラムが返却されていない)

/**
 * 线程死锁
 */
class A{};
class B{};
public class Stack4ThreadDeadLock {
    static A a = new A();
    static B b = new B();


    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (a) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (b) {
                synchronized (a) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
    }
}

三、ローカルメソッドスタック Navite Method Stacks

ネイティブ キーワードを含むメソッドでは、JAVA がローカルの C または C++ メソッドを呼び出す必要があります。これは、JAVA がオペレーティング システムの最下層と直接やり取りできない場合があるためです。そのため、ネイティブ メソッド スタックを使用して、ネイティブ キーワードを含むメソッドを提供する必要があります。

共通のルート クラス Object には、多くの navite 変更方法があります。

ローカル メソッド スタックの機能と特性は仮想マシン スタックに似ており、どちらもスレッド分離の特性を持ち、StackOverflowError および OutOfMemoryError 例外をスローできます。

違いは、ネイティブ メソッド スタックによって提供されるオブジェクトは JVM によって実行されるネイティブ メソッドであるのに対し、仮想マシン スタックは JVM によって実行される Java メソッドを提供することです。HotSpot 仮想マシンは、仮想マシン スタックとローカル メソッド スタックを区別しません。この 2 つは 1 つのピースです。

四、ヒープ ヒープ

JVM によって管理される最大のメモリ領域は、オブジェクト インスタンスを格納し、スレッド共有領域です (new キーワードにより、オブジェクトはヒープ メモリを使用して作成されます)。

 ヒープは、ガベージ コレクターによって管理される主要な領域であるため、「GC ヒープ」という名前が付けられています。

JAVA ヒープの分類:

(1) 記憶回復の観点から、新世代 (Eden 空間、From Survivor 空間、To Survivor 空間) と旧世代 (Tenured Gen) に分けることができます。

  • ヒープ メモリは、两块1 つのピース年轻代と別のピースに分割されます老年代

  • 若い世代はさらにEdenとに分けられますsurvivorスペース サイズの比率はデフォルトで 8:2 です。

  • サバイバルエリアはs0(From Spaces1(To Spaceに分かれていますこの 2 つのスペースはまったく同じサイズで、1 対 1 の比率の双子です。

若い世代はエデンエリアとサバイバーエリアに分かれています。Survivor エリアは、From Space と To Space で構成されます。Eden エリアは大きな容量を占有し、Survivor エリアは小さな容量を占有します.デフォルトの比率は 8:1:1 です

古い世代と若い世代のデフォルトの比率は 2:1 です。

(2) メモリ割り当ての観点から、メモリ割り当て時のスレッド セーフの問題を解決するために、複数のスレッド プライベート アロケーション バッファ (TLAB) をスレッドが共有する JAVA ヒープに分割することができます。

JAVA ヒープは、論理的に連続している限り、物理的に不連続なメモリ空間にあってもかまいません。

実行時のヒープ メモリのサイズはパラメータ -Xmx -Xms で指定でき、ヒープ メモリ領域が不十分な場合は OutOfMemoryError 例外がスローされます。

(1) ヒープメモリオーバーフロー OutOfMemoryError

/**
 * 堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m
 */
public class Heap1OutOfMemoryError {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true) {
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

java.lang.OutOfMemoryError: Java ヒープ領域 

(2) ヒープメモリ診断

/**
 * 堆内存
 */
public class Heap2Memory {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        //在此休眠30s 是为了 输出 jmap - heap 进程id 命令
        Thread.sleep(30000);

        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb

        System.out.println("2...");
        Thread.sleep(30000);

        array = null;

        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}

1.jpsツール

現在のシステムに存在する Java プロセスを確認する

jps

2. jmap ツール

ヒープ メモリの使用状況を表示する

コンソールがそれぞれ 1、2、および 3 を出力した後、jmap - heap process id コマンドを実行します。

jmap - heap 进程id

次のエラーが発生する場合があります。

Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.291-b10. Target VM is 25.342-b07
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.291-b10. Target VM is 25.342-b07

解決:

1.使用時のパスを指定する 

D:\JDK\jdk1.8.0_291\bin\jmap -heap 21352

或

C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap

2. と同じように java -version的JDKコマンドを保持します程序运行的JDK

3 回の出力は次のとおりです。 

D:\Java\JavaProject\jvm-demo\myjvm>jps
23092 RemoteMavenServer36
9460 Jps
20664 Launcher
23640 Heap2Memory
6040 Launcher
13020 RemoteMavenServer36
15852

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 6074064 (5.7926788330078125MB)
   free     = 94589232 (90.20732116699219MB)
   6.034040451049805% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 0 (0.0MB)
   free     = 267386880 (255.0MB)
   0.0% used

1706 interned Strings occupying 175328 bytes.

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 16559840 (15.792694091796875MB)
   free     = 84103456 (80.20730590820312MB)
   16.45072301228841% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 0 (0.0MB)
   free     = 267386880 (255.0MB)
   0.0% used

1707 interned Strings occupying 175376 bytes.

D:\Java\JavaProject\jvm-demo\myjvm>C:\Users\zhangm\.jdks\corretto-1.8.0_342\bin\jmap -heap 23640
Attaching to process ID 23640, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.342-b07

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6377439232 (6082.0MB)
   NewSize                  = 133169152 (127.0MB)
   MaxNewSize               = 2125463552 (2027.0MB)
   OldSize                  = 267386880 (255.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 100663296 (96.0MB)
   used     = 4026576 (3.8400421142578125MB)
   free     = 96636720 (92.15995788574219MB)
   4.000043869018555% used
From Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
To Space:
   capacity = 16252928 (15.5MB)
   used     = 0 (0.0MB)
   free     = 16252928 (15.5MB)
   0.0% used
PS Old Generation
   capacity = 267386880 (255.0MB)
   used     = 830432 (0.791961669921875MB)
   free     = 266556448 (254.20803833007812MB)
   0.31057320389093135% used

1691 interned Strings occupying 174248 bytes.

 

3.jconsole ツール

グラフィカルインターフェース、多機能監視ツール、継続監視可能

プログラムを実行すると、コンソールに次のコマンドが出力されます

jconsole

 

4. jvisualvm ツール

5. メソッドエリア メソッドエリア

公式サイトアドレス

第2章 Java 仮想マシンの構造

メソッド領域は Java ヒープと同様に、各スレッド共有するメモリ領域であり、クラス情報、定数、静的変数、仮想マシンによってロードされたインスタント コンパイラによってコンパイルされたコードなどのデータを格納するために使用されます。

JDK8より前は、メソッド領域の実装は永久世代と呼ばれ、ヒープの一部をメソッド領域として使用していました

JDK8 以降、永続世代の実装が削除され、メタスペースの実装が置き換えられました. メタスペースは、ヒープの一部ではなく、オペレーティング システムの一部 (一部のメモリ) をメソッド領域として使用しました.

(1) メソッド領域の構造

 (2) メソッド領域のメモリオーバーフロー

1. 1.8 より前のバージョンでは、永続的な世代のメモリ オーバーフローが発生していました。

永久世代メモリ オーバーフロー java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m

2. 1.8 以降では、メタスペースでメモリ オーバーフローが発生します。

メタスペース メモリ オーバーフロー java.lang.OutOfMemoryError: Metaspace
-XX:MaxMetaspaceSize=8m

 (3) 実行時定数プール

1.コンスタントプール

定数プールは、クラス定数プールとも呼ばれます。各ファイル.javaを生成するためにコンパイルファイルには、定数プールが含まれます。この定数プールは、クラス ファイルで定義され、ファイルがコンパイルされた後は変更されません。変更されないため、静的定数プールと呼ばれます.class.class.java

定数プールはテーブルであり、仮想マシン命令は、この定数テーブルに従って実行するクラス名、メソッド名、パラメーターの型、リテラル値
、およびその他の情報を見つけます。

2. ランタイム定数プール

定数プールは *.class ファイルにあります。クラスのバイトコードがメモリにロードされると、その定数プール情報は、実行時定数プールと呼ばれるメモリの一部と、その中の符号地址変数に集中します。为真实地址

ランタイム定数プールとクラスファイルの定数プールは 1 対 1 で対応し、クラスファイルの定数プールによって構築されます。

ランタイム定数プールには、シンボリック参照と静的定数の 2 つのタイプがあります。

その中で、静的定数はその後の解析を必要としませんが、シンボリック参照はさらに解析を必要とします

静的定数、記号参照

String site="www.com"

文字列 "www.com" は静的定数と見なすことができます。これは変更されず、それが何であれ表示されるためです。

上記の文字列の名前「site」はシンボル参照であり、実行時に解析する必要があります。これは、site の値が変更される可能性があり、実際の値を最初に決定することができず、動的実行中に解析する必要があるためです。

Hello World の基本的な Java プログラムを作成し、それを実行して .class バイトコード ファイルにコンパイルし、コンソールで .class ファイルが配置されているディレクトリに切り替えて実行します。

javap -v HelloWorld

コンソール出力は次のとおりです。

D:\Java\JavaProject\jvm-demo\myjvm>cd out/production/myjvm/com/mycompany

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm\com\mycompany>javap -v HelloWorld
警告: 二进制文件HelloWorld包含com.mycompany.HelloWorld
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/HelloWorld.class
  Last modified 2022-9-27; size 562 bytes
  MD5 checksum 56139c042931911e7cea84a4ece0987c
  Compiled from "HelloWorld.java"
public class com.mycompany.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello World!
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/mycompany/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/mycompany/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World!
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/mycompany/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.mycompany.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

3. 静的定数

ランタイム定数プールの静的定数は、クラス ファイルの constant_pool から構築されます。次の 2 つの部分に分けることができます。

文字列定数数値定数

(1)文字列定数

String 定数は String オブジェクトへの参照であり、クラスの CONSTANT_String_info 構造から構築されます。

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

string_index に対応するクラス定数プールの内容は CONSTANT_Utf8_info 構造体です 

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

CONSTANT_Utf8_info は、作成する文字列オブジェクトのバリアント UTF-8 エンコーディングです。 

(2)数値定数

数値定数は、クラス ファイル内の CONSTANT_Integer_info、CONSTANT_Float_info、CONSTANT_Long_info、および CONSTANT_Double_info から構築されます。

4. 記号参照

シンボリック参照も、クラスの constant_pool から構築されます。

クラスおよびインターフェイスへのシンボリック参照は、CONSTANT_Class_info から取得されます。

クラスおよびインターフェイスのフィールドへの参照は、CONSTANT_Fieldref_info から取得されます。

クラス内のメソッドへの参照は、CONSTANT_Methodref_info から取得されます。

インターフェイス内のメソッドへの参照は、CONSTANT_InterfaceMethodref_info から取得されます。

メソッド ハンドルへの参照は、CONSTANT_MethodHandle_info から取得されます。

メソッド タイプへの参照は、CONSTANT_MethodType_info から取得されます。

動的に計算された定数へのシンボリック参照は、CONSTANT_MethodType_info から取得されます。

動的に計算された呼び出しサイトへの参照は、CONSTANT_InvokeDynamic_info から取得されます

(4) 文字列テーブル

1. StringTable の特徴

定数プール内の文字列は単なる記号であり、初めて使用するときにオブジェクトになります

文字列プール メカニズムを使用して、文字列オブジェクトが繰り返し作成されないようにする

文字列変数のスプライシングの原則は StringBuilder (1.8) です

文字列定数スプライシングの原則はコンパイル時の最適化です

internメソッドは、文字列プールにない文字列オブジェクトを積極的に文字列プールに入れます。

[1] 1.8 この文字列オブジェクトを文字列プールに入れてみます。ある場合は文字列プールに入れず、そうでない場合は文字列プールに入れ、文字列内のオブジェクトの参照プールが返ってきます

[2] 1.6 この文字列オブジェクトを文字列プールに入れてみます。ある場合は入れません。ない場合は、このオブジェクトのコピー (新しい文字列オブジェクト) を作成し、文字列プールに入れます。 、そして文字列を入れます プール内のオブジェクトが戻ります

JDK1.8

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTable1 {
    public static void main(String[] args) {
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 b 符号变为 "b" 字符串对象
        // ldc #4 会把 ab 符号变为 "ab" 字符串对象

        String s1 = "a"; // 懒惰的
        String s2 = "b";

        String s3 = "ab";
        String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString() ----> new String("ab") 堆内存中新对象
        String s5 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为ab(单纯的字符串拼接)

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
    }
}
D:\Java\JavaProject\jvm-demo\myjvm>cd out/production/myjvm/com/mycompany/stringtable
D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm\com\mycompany\stringtable>javap -v Stringtable1
警告: 二进制文件Stringtable1包含com.mycompany.stringtable.StringTable1
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/stringtable/Stringtable1.class
  Last modified 2022-9-27; size 1045 bytes
  MD5 checksum 92716b83ac90d0a1d2798c17959679f0
  Compiled from "StringTable1.java"
public class com.mycompany.stringtable.StringTable1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#36        // java/lang/Object."<init>":()V
   #2 = String             #37            // a
   #3 = String             #38            // b
   #4 = String             #39            // ab
   #5 = Class              #40            // java/lang/StringBuilder
   #6 = Methodref          #5.#36         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#41         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#42         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Fieldref           #43.#44        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Z)V
  #11 = Class              #47            // com/mycompany/stringtable/StringTable1
  #12 = Class              #48            // java/lang/Object
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lcom/mycompany/stringtable/StringTable1;
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               args
  #23 = Utf8               [Ljava/lang/String;
  #24 = Utf8               s1
  #25 = Utf8               Ljava/lang/String;
  #26 = Utf8               s2
  #27 = Utf8               s3
  #28 = Utf8               s4
  #29 = Utf8               s5
  #30 = Utf8               StackMapTable
  #31 = Class              #23            // "[Ljava/lang/String;"
  #32 = Class              #49            // java/lang/String
  #33 = Class              #50            // java/io/PrintStream
  #34 = Utf8               SourceFile
  #35 = Utf8               StringTable1.java
  #36 = NameAndType        #13:#14        // "<init>":()V
  #37 = Utf8               a
  #38 = Utf8               b
  #39 = Utf8               ab
  #40 = Utf8               java/lang/StringBuilder
  #41 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #42 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
  #43 = Class              #55            // java/lang/System
  #44 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;
  #45 = Class              #50            // java/io/PrintStream
  #46 = NameAndType        #58:#59        // println:(Z)V
  #47 = Utf8               com/mycompany/stringtable/StringTable1
  #48 = Utf8               java/lang/Object
  #49 = Utf8               java/lang/String
  #50 = Utf8               java/io/PrintStream
  #51 = Utf8               append
  #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #53 = Utf8               toString
  #54 = Utf8               ()Ljava/lang/String;
  #55 = Utf8               java/lang/System
  #56 = Utf8               out
  #57 = Utf8               Ljava/io/PrintStream;
  #58 = Utf8               println
  #59 = Utf8               (Z)V
{
  public com.mycompany.stringtable.StringTable1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/stringtable/StringTable1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=6, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        36: aload_3
        37: aload         4
        39: if_acmpne     46
        42: iconst_1
        43: goto          47
        46: iconst_0
        47: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        50: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        53: aload_3
        54: aload         5
        56: if_acmpne     63
        59: iconst_1
        60: goto          64
        63: iconst_0
        64: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        67: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 8: 6
        line 9: 9
        line 10: 29
        line 12: 33
        line 13: 50
        line 14: 67
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      68     0  args   [Ljava/lang/String;
            3      65     1    s1   Ljava/lang/String;
            6      62     2    s2   Ljava/lang/String;
            9      59     3    s3   Ljava/lang/String;
           29      39     4    s4   Ljava/lang/String;
           33      35     5    s5   Ljava/lang/String;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 46
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String
, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */

          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringTable1.java"

 intern() メソッド

1.8 この文字列オブジェクトを文字列プールに入れてみます。ある場合は文字列プールに入れず、そうでない場合は文字列プールに入れ、文字列プールのオブジェクトを返します

1.6 この文字列オブジェクトを文字列プールに入れてみます。ある場合は入れません。そうでない場合は、このオブジェクトのコピーを作成し、文字列プールに入れ、文字列プールにオブジェクトを返します。

JDK1.8

public class StringTable2 {
    public static void main(String[] args) {
        //  ["ab", "a", "b"]

        // 对比 s == x false
        //String x = "ab";
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        //1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        //1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
        String s2 = s.intern();

        // 对比 s == x true
        String x = "ab";

        System.out.println( s2 == x);//true
        System.out.println( s == x );
    }
}
/**
 * 字符串相关分析
 */
public class StringTable3 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // 字符串拼接 ----> "ab";javac 在编译器的优化,结果在编译器已经确定的
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() ----> new String("ab")
        String s5 = "ab";
        /*
        JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        JDK1.6:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则会复制一个对象放入串池中,会把串池对象返回
        * */
        String s6 = s4.intern();

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true



        // 堆中 new String("c") ; new String("d") ;new StringBuilder().append("c").append("d").toString() ----> new String("cd")
        String x2 = new String("c") + new String("d");

        //对比 x1 == x2 false
//        String x1 = "cd";
//        x2.intern();

        //调换最后两行代码位置(true) 对比 x1 == x2 true
        //JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        x2.intern();
        String x1 = "cd";

        //JDK1.8 如果调换最后两行代码位置(true)
        System.out.println(x1 == x2);

    }
}

JDK1.6

public class StringTable2 {
    public static void main(String[] args) {

        // 串池中 ["ab", "a", "b"]
        //对比 s == x false
        //String x = "ab";
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        //1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
        //1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
        String s2 = s.intern();
        // s 拷贝一份,放入串池(一个新对象;s指向的"ab"地址和s2指向的"ab"地址不是同一份)

        // 串池中 ["a", "b","ab"]
        //对比 s == x false
        String x = "ab";

        System.out.println( s2 == x);//true
        System.out.println( s == x );//false
    }
}
/**
 * 字符串相关分析
 */
public class StringTable3 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // 字符串拼接 ----> "ab";javac 在编译器的优化,结果在编译器已经确定的
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() ----> new String("ab")
        String s5 = "ab";
        /*
        JDK1.8:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则放入串池中,会把串池对象返回
        JDK1.6:intern()方法将这个字符串对象尝试放入StringTable串池中;如果有不会放入,如果没有则会复制一个对象放入串池中,会把串池对象返回
        * */
        String s6 = s4.intern();

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true

        // 堆中 new String("c") ; new String("d") ;new StringBuilder().append("c").append("d").toString() ----> new String("cd")
        String x2 = new String("c") + new String("d");

        // 对比 x1 == x2 false
        String x1 = "cd";
        x2.intern();

        //如果调换最后两行代码位置 对比 x1 == x2 false
//        x2.intern();
//        String x1 = "cd";

        //JDK1.6 如果调换最后两行代码位置(false)
        System.out.println(x1 == x2);//false
    }
}

2. StringTable の場所

jdk1.6 StringTable の場所は永久世代で、1.8 StringTable の場所はヒープです。

JDK1.8 

/**
 * StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 *      java.lang.OutOfMemoryError: Java heap space
 *      单独设置 -Xmx10m 报错
 *      Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
 *      java.lang.OutOfMemoryError: GC overhead limit exceeded ,超出了GC开销限制。科普了一下,这个是JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。一般是因为堆太小,导致异常的原因:没有足够的内存。
 *      官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常
 * 在jdk6下设置 -XX:MaxPermSize=10m
 *      java.lang.OutOfMemoryError: PermGen space 
 */
public class StringTable4Location {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

java.lang.OutOfMemoryError: GC オーバーヘッドの制限を超えました

GC オーバーヘッドの制限を超えました。一般的な科学では、これは JDK6 によって追加された新しいタイプのエラーです。これは、GC が小さなスペースを解放するのに多くの時間がかかる場合に発生します。これは保護メカニズムです。通常、ヒープが小さすぎるため、例外の原因: 十分なメモリがありません 

これの正式な定義: この例外は、時間の 98% 以上が GC に費やされ、ヒープ メモリの 2% 未満が再利用された場合にスローされます。 

java.lang.OutOfMemoryError: Java ヒープ領域

JDK1.6 

java.lang.OutOfMemoryError: PermGen スペース 

3. StringTable ガベージ コレクション

/**
 * StringTable 垃圾回收
 * 在在JDK1.8下VM设置
 *      -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 *          -Xmx10m          设置虚拟机堆内存大小
 *          -XX:+PrintStringTableStatistics  打印字符串表的统计信息
 *          -XX:+PrintGCDetails -verbose:gc  打印垃圾回收详细信息参数
 * 在JDK1.6下VM设置
 *      -XX:MaxPermSize=10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 *          -XX:MaxPermSize=10m      设置虚拟机堆内存大小
 *          -XX:+PrintStringTableStatistics    打印字符串表的统计信息
 *          -XX:+PrintGCDetails -verbose:gc    打印垃圾回收详细信息参数
 */
public class StringTable5GC {
    // 字符串常量池中默认1688个字符串 
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
//            for (int j = 0; j < 100000; j++) { // j=100, j=10000
//                String.valueOf(j).intern();
//                i++;
//            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }

    }
}

JDK1.8

(1) try 何もしない

Heap
 PSYoungGen      total 2560K, used 727K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 11% used [0x00000000ffd00000,0x00000000ffd3bc80,0x00000000fff00000)
  from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 379K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 5% used [0x00000000ff600000,0x00000000ff65efb8,0x00000000ffd00000)
 Metaspace       used 3214K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: '127.0.0.1:62455', transport: 'socket'
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     13428 =    322272 bytes, avg  24.000
Number of literals      :     13428 =    605144 bytes, avg  45.066
Total footprint         :           =   1087504 bytes
Average bucket size     :     0.671
Variance of bucket size :     0.668
Std. dev. of bucket size:     0.817
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1688 =     40512 bytes, avg  24.000
Number of literals      :      1688 =    174104 bytes, avg 103.142
Total footprint         :           =    694720 bytes
Average bucket size     :     0.028
Variance of bucket size :     0.028
Std. dev. of bucket size:     0.168
Maximum bucket size     :         3

デフォルトでは、文字列定数プールには 1688 個の文字列があります 

(2) try で 100 回ループ、文字列定数の数 + 100

(3) try で 100,000 ループが発生し、ガベージ コレクション メカニズム GC がトリガーされ、文字列は 28,000 以上しかない

JDK1.6

(1) try 何もしない

0
Heap
 def new generation   total 4928K, used 1243K [0x10030000, 0x10580000, 0x15580000)
  eden space 4416K,  28% used [0x10030000, 0x10166d20, 0x10480000)
  from space 512K,   0% used [0x10480000, 0x10480000, 0x10500000)
  to   space 512K,   0% used [0x10500000, 0x10500000, 0x10580000)
 tenured generation   total 10944K, used 0K [0x15580000, 0x16030000, 0x20030000)
   the space 10944K,   0% used [0x15580000, 0x15580000, 0x15580200, 0x16030000)
 compacting perm gen  total 12288K, used 2537K [0x20030000, 0x20c30000, 0x20c30000)
   the space 12288K,  20% used [0x20030000, 0x202aa608, 0x202aa800, 0x20c30000)
No shared spaces configured.
Disconnected from the target VM, address: '127.0.0.1:63518', transport: 'socket'
SymbolTable statistics:
Number of buckets       :   20011
Average bucket size     :       0
Variance of bucket size :       0
Std. dev. of bucket size:       1
Maximum bucket size     :       6
StringTable statistics:
Number of buckets       :    1009
Average bucket size     :       1
Variance of bucket size :       1
Std. dev. of bucket size:       1
Maximum bucket size     :       7

(2) try で 100,000 サイクル、ガベージ コレクション メカニズム GC をトリガー、28,000+ 文字列のみ

4. StringTable のパフォーマンス チューニング

1. -XX:StringTableSize=バケット数を調整

バケットサイズを設定します (バケットは配列インデックスの添字要素です); JDK1.6 のデフォルトは 1009、JDK1.7 以降のデフォルトは 60013、JDK1.8 以降では 1009 が設定可能な最小値です

文字列定数プールの最下層は HashTable です (HashTable クラスは、キーを対応する値にマップするハッシュ テーブルを実装します。HashTable の最下層は HashMap と同じです; JDK1.6 配列 + 一方向リンク リスト; JDK1. 8 配列 + 一方向リンク リスト + 赤黒ツリー)、定数プールのサイズを合理的に増やすと、ハッシュ競合の問題が解決されます。

バケットの数が多いほど、連結リストまたは配列インデックスによって添字付けされた赤黒ツリー要素を効率的に検索できます (連結リストの要素が少ないほど、走査時間は短くなります)。

480,000 近くの文字列を含むテキスト ファイルをコピーし、既定の構成に従って実行すると、336 ミリ秒かかります

/**
 * StringTableSize串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
 *      -Xms500m    设置堆内存最小值
 *      -Xmx500m    设置堆内存最大值
 *      -XX:+PrintStringTableStatistics     字符串常量池统计信息
 *      -XX:StringTableSize=1009
 *          设置桶大小(桶即数组索引下标元素);JDK1.6默认为1009,JDK1.7之后默认为60013,JDK1.8开始1009是可以设置的最小值
 *          字符串常量池底层为HashTable(HashTable类实现一个哈希表,该哈希表将键映射到相应值;HashTable底层与HashMap原理相同;JDK1.6 数组+单向链表;JDK1.8 数组 + 单向链表 + 红黑树),合理增大常量池大小会解决Hash冲突问题
 */
public class StringTable6Optimize {
    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("myjvm/linux.words"), "utf-8"))) {
            String line = null;
            long start = System.nanoTime();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("花费时间:" + (System.nanoTime() - start) / 1000000 + "毫秒");
        }
    }
}

VM パラメータを設定し、バケット サイズを設定します StringTableSize = 1009 最小値、8066 ミリ秒かかります

VM パラメータを設定し、バケット サイズを設定 StringTableSize = 200000、314 ミリ秒かかります

2.いくつかの文字列オブジェクトをプールに入れるかどうか

public class StringTable7OptimizeIntern {
    public static void main(String[] args) throws IOException {

        List<String> address = new ArrayList<>();
        //System.in.read();
        for (int i = 0; i < 10; i++) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("myjvm/linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    address.add(line.intern());
                }
                System.out.println("花费时间:" + (System.nanoTime() - start) / 1000000 + "毫秒");
            }
        }
        //System.in.read();
    }
}

6、ダイレクトメモリ ダイレクトメモリ

ダイレクト メモリ ダイレクト メモリは JVM 管理に属しておらず、オペレーティング システムのメモリです。

(1) データ バッファーの NIO 操作で一般的に使用されます (たとえば、ByteBuffer は直接メモリを使用します)。

(2) 割り当てと回復のコストは高いが、読み取りと書き込みのパフォーマンスは高い

(3) JVMメモリリカバリ管理対象外

(1) ファイル(IO)の読み書き処理

1.伝統的な方法

Java 自体には、ディスクを読み書きする機能はありません. ディスクの読み書きを実現するには、オペレーティング システムによって提供される関数 (つまり、ローカル メソッド) を呼び出す必要があります. ここで、CPU の状態は、ユーザーの状態 (Java ) カーネル状態 (システム) [システムによって提供される関数の後に呼び出す]

メモリ側にもいくつか関連する操作があります. カーネル状態に切り替えた後, 彼は CPU 機能を使用して実際にディスクファイルの内容を読み取ることができます. カーネル状態では, 内容を読み取った後, 彼はシステム バッファと呼ばれるメモリ内のバッファを描画すると、ディスクの内容が最初にシステム バッファに読み込まれます (バッチで読み込まれます)。システム バッファ内の Java コードは実行できないため、Java はシステムバッファ ヒープメモリに Java バッファを割り当てます。つまり、コード内の new byte[size] です。読み取ったばかりのストリーム内のデータに Java コードがアクセスできるようにするには、データをシステム バッファから Java バッファに間接的に読み込む必要があります。次に、CPU の状態がユーザー状態に切り替わり、 Java 出力ストリームの書き込み操作は、読み取りと書き込みを繰り返すだけで、ファイル全体をターゲットの場所にコピーします。

2 つのメモリと 2 つのバッファがあるため、つまり、システム メモリと Java ヒープ メモリの両方にバッファがあるため、データを読み取るときに 2 つのコピーに格納する必要があります。 Javaコードはそれらにアクセスできないため、システムバッファのデータがJavaバッファに読み込まれ、不要なデータコピーが発生し、効率はあまり高くありません

つまり、Java はファイル管理を直接操作できません。カーネル モードに切り替え、ローカル メソッドを使用して操作し、ディスク ファイルを読み取る必要があります。システム メモリにバッファが作成され、システム バッファにデータが読み込まれます。 、次にシステム バッファ データが Java ヒープ メモリにコピーされます。欠点は、データが 2 つのコピー (システム メモリに 1 つ、Java ヒープに 1 つ) に格納されるため、不要なコピーが発生することです。

2. directBuffer を使用する

ByteBuffer がallocateDirectメソッドを呼び出すと、オペレーティング システム側のバッファ領域、つまりダイレクト メモリが描画されます.この領域と以前の領域の違いは、オペレーティング システムによって割り当てられたメモリに Java コードから直接アクセスできることです。つまり、システムにアクセスでき、Javaコードもアクセスできます。つまり、システムメモリとJavaコードの両方で共有できるメモリ領域であり、これがダイレクトメモリです。

つまり、ディスク ファイルがダイレクト メモリに読み取られた後、Java コードがダイレクト メモリに直接アクセスするため、従来のコードよりもバッファ内のコピー操作が 1 回削減されるため、速度が 2 倍になります。これは、この種のファイルの io 操作に適しているダイレクト メモリの利点でもあります。

つまり、ダイレクト メモリは、オペレーティング システムと Java コードの両方がアクセスできる領域であり、システム メモリから Java ヒープ メモリにコードをコピーする必要がなくなり、効率が向上します。

/**
 * ByteBuffer 作用
 */
public class ByteAndDirectBuffer {
    static final String FROM = "D:\\文件\\SQL基本介绍.avi";
    static final String TO = "D:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:71.7764;53.8123;107.987
        directBuffer(); // directBuffer 用时:40.2163;30.3857;46.0605
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}

(2) メモリオーバーフロー OOM

java.lang.OutOfMemoryError: ダイレクト バッファ メモリ

/**
 * 直接内存溢出
 * java.lang.OutOfMemoryError: Direct buffer memory
 */
public class Direct2OutOfMemory {
    static int _100Mb = 1024 * 1024 * 100;

    public static void main(String[] args) {
        List<ByteBuffer> list = new ArrayList<>();
        int i = 0;
        try {
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
                list.add(byteBuffer);
                i++;
            }
        } finally {
            System.out.println(i);
        }
        /*
        方法区是jvm规范
            jdk6 中对方法区的实现称为永久代
            jdk8 对方法区的实现称为元空间
        * */
    }
}

(3) 分配と回収の原則

直接メモリは JVM によって管理されないため、jmap、jconsole、jvisualvm などのツールを使用してメモリ使用量を監視することはできません。「タスク マネージャ」で確認する必要があります。

/**
 * 禁用显式回收对直接内存的影响
 */
public class Direct3GC {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * -XX:+DisableExplicitGC 显式的
     */
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);

        System.out.println("分配完毕...");

        System.in.read();

        System.out.println("开始释放...");

        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC

        System.in.read();
    }
}

メモリの割り当てを開始します 

空きメモリ 

ノート:

ここには誤解があります。コールが表示されているとは思わないでください 

System.gc(); // 明示的なガベージ コレクション、フル GC

ダイレクト メモリが回収されると、ダイレクト メモリが JVM によって管理されていると見なされますが、実際には、基になるUnsafeオブジェクトが役割を果たします。

VM が設定されている場合

-XX:+DisableExplicitGC

System.gc() が無効になり、コレクションが失敗します

Javaのガベージコレクションが行われていないため、byteBufferはnullですが、十分なメモリがあるため生きています。彼は生きているので、対応する直接メモリ (ByteBuffer.allocateDirect(-1Gb)) は回収されておらず、Windows はタスク マネージャーからそれを見ることができます (上記のコードを実行した後、タスク マネージャーは Java プロセスを表示し、1G が割り当てられます)。ここでは、そのプロセスが占有するメモリも 1G です)

System.gc() を無効にした後、他のコードは大きな影響を受けないことがわかりますが、表示されたメソッドを使用して Bytebuffer をリサイクルすることができないため、ByteBuffer は実際のガベージ コレクションまでしか待機できないため、直接メモリが影響を受けます。がクリーンアップされ、対応するダイレクト メモリもクリーンアップされます。

これにより、ダイレクトメモリが大量に占有され、長時間解放できなくなる現象が発生しました。直接メモリの使用量が多い場合、直接メモリを解放する際に Unsafe オブジェクトの freeMemory メソッドを直接呼び出す方法が直接メモリの管理方法ですが、最終的にはプログラマが手動で直接メモリを管理することになるので、 Unsafe の関連メソッド

1. 安全でない オブジェクトの割り当てとメモリの回復

Unsafe オブジェクトは直接メモリの割り当てと回復を完了し、回復には freeMemory メソッドを積極的に呼び出す必要があります

/**
 * 直接内存分配的底层原理:Unsafe
 */
public class Direct4Unsafe {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        Unsafe unsafe = getUnsafe();
        // 分配内存
        long base = unsafe.allocateMemory(_1Gb);
        unsafe.setMemory(base, _1Gb, (byte) 0);
        System.out.println("分配完毕...");
        System.in.read();

        // 释放内存
        unsafe.freeMemory(base);
        System.out.println("释放...");
        System.in.read();
    }

    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe) f.get(null);
            return unsafe;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

メモリを割り当てる 

解放されたメモリを解放する

2. Unsafe の割り当てと回復の原則

Unsafe オブジェクトは直接メモリの割り当てと回復を完了し、回復には freeMemory メソッドを積極的に呼び出す必要があります

ByteBuerの実装クラス内では、 Cleaner (仮想参照) を使用して ByteBuerオブジェクト を監視します  . ByteBuer オブジェクトがガベージ コレクションされると、  ReferenceHandlerスレッドは Cleaner の cleanメソッドを介してfreeMemoryメソッドを 呼び出し 、ダイレクト メモリを解放します.

ByteBuffer.allocateDirect(_1Gb);


public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>{
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
}


class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer{
    DirectByteBuffer(int cap) {                   // package-private

        ......

        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        
        ......

        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;

    }

    
    private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }
}

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/127066302