JVM メモリ構造と GC チューニング

1. JVM の概要

1.1 JVM とは何ですか?

Java 仮想マシン (Java 仮想マシン)
ライトワンスでどこでも実行可能
ここに画像の説明を挿入

1.2 JDK JRE JVM

Java 公式 Web サイト: https://docs.oracle.com/javase/8/
「リファレンス」 -> 「開発者ガイド」 -> 「
https://docs.oracle.com/javase/8/docs/index.html 」 に移動します。

JDK 8 は JRE 8 のスーパーセットであり、JRE 8 のすべての機能に加えて、
アプレットやアプリケーションの開発に必要なコンパイラやデバッガなどのツールが含まれています。JRE 8 は、Java プログラミング言語で記述されたアプレットやアプリケーションを実行するためのライブラリ、Java 仮想マシン (JVM)、およびその他のコンポーネントを提供します。JRE には、標準 Java コンポーネントと非標準 Java コンポーネントの両方を含む、Java SE 仕様で必須ではないコンポーネントが含まれていることに注意してください。

ここに画像の説明を挿入

2 つの JVM 動作メカニズム

2.1 ソースコードからクラスファイルへ

2.1.1 ソースコードのデモ

class Person{
    
    
	private String name="刘同学";
	private int age;
	private final double salary=100;
	private static String address;
	private final static String hobby="Programming";
	private static Object obj=new Object();
	public void say(){
    
    
		System.out.println("person say...");
	}
	public static int calc(int op1,int op2){
    
    
	op1=3;
	int result=op1+op2;
	Object obj=new Object();
		return result;
	}
	public static void main(String[] args){
    
    
		calc(1,2);
	}
}

コンパイル: javac -g:vars person.java —> person.class

2.1.2 プリコンパイル

Person.java -> 字句アナライザー -> トークン ストリーム -> 構文アナライザー -> 構文ツリー/抽象構文ツリー
-> セマンティック アナライザー -> 注釈付き抽象構文ツリー -> バイトコード ジェネレーター -> Person.class ファイル

2.1.3 クラスファイル(クラスファイル)

2.1.3.1 16 進数

カフェベイビー 0000 0034 003f 0a00 0a00 2b08 002c 0900 0d00 2d06 4059 0000
0000 0000 0900 0d00
2e09 002f 0030 0800 310a 0032 0033 070 0 340a 000d 0035 0900 0d00 3607 0037 0100 046e
616d 6501 0012 4c6a 6176 612f 6c61 6e67 2f53 7472
696e
673b 0100 0361
6765 0100 0149 0100 0673 616c 6172
7901 0001 4401 000d 436f 6e73 7461 6e74

2.1.3.2 ClassFile 構造

公式ウェブサイト: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

ClassFile { u4 マジック; u2 マイナーバージョン; u2 メジャー バージョン; u2 定数プール数; cp_info constant_pool[constant_pool_count-1]; u2 アクセスフラグ; u2 このクラス; u2 スーパークラス; u2 インターフェイス数; u2 インターフェイス[インターフェイス数]; u2 フィールド数; field_info フィールド[フィールド数]; u2 メソッド数; Method_info メソッド[メソッド数]; u2 属性数; 属性情報 属性[属性数]; }
















2.1.3.3 簡易分析

u4 :カフェベイブ

マジック: マジック アイテムは、クラス ファイル
形式を識別するマジック ナンバーを提供します。

u2+u2:0000+0034、34 は 10 進数の 52 に相当します。これは JDK8 を意味します。

マイナーバージョン
メジャーバージョン

u2:003f=63(10進数)

constant_pool_count:
constant_pool_count 項目の値は、
constant_pool テーブル内のエントリの数に 1 を加えたものと等しくなります。

定数プール内の数が 62 であることを示します。
cp_info constant_pool[constant_pool_count-1]

constant_pool は、さまざまな文字列定数、クラス名とインターフェイス名、フィールド名、および ClassFile 構造体とそのサブ構造内で参照されるその他の定数を表す構造体のテーブルです。各 constant_pool テーブル エントリの形式は、その最初の「タグ」バイトによって示されます。
constant_pool テーブルには、1 から constant_pool_count - 1 までのインデックスが付けられます。

定数プールには主に、リテラル参照とシンボリック参照という 2 つの側面が保存されます。

リテラル数量: テキスト文字列、最終的な変更など。
シンボル参照: クラスとインターフェイスの完全修飾名、フィールド名と記述子、メソッド名と記述子。

2.1.3.4 Javapの検証

JDKに付属のコマンド

javap -h

上記のクラスファイル構造の最初のいくつかの部分が正しいことを検証できます。

javap -v -p Person.class を逆コンパイルすると、バイトコード情報や命令などの情報が表示されます

クラス ファイルと比較すると、JVM はオペレーティング システムとして理解でき、JVM と比較すると、クラス ファイルはアセンブリ言語またはマシン語として理解できます。

2.1.3.5 継続的な分析

上記の分析により、定数プール内の定数の数は 62 であることがわかります。次に、この 62 個の定数を詳細に分析してみましょう。cp_info constant_pool[constant_pool_count-1] が、この部分に含まれる情報です。cp_info は、実際にはテーブル形式です。すべての constant_pool テーブル エントリは、
次の一般的な形式になります。

cp_info{ u1tag; u1info[]; }


公式ウェブサイト: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

ここに画像の説明を挿入

(1) u1 をカウントダウンします。つまり、0a->10: これは CONSTANT_Methodref を表し、これがメソッド参照であることを示します。

CONSTANT_Fieldref_info {
u1 タグ;
u2 クラスインデックス;
u2 名前とタイプインデックス;
}

u2 と u2 u2 をカウントダウンします。つまり、00 0a->10: class_indexを表し、定数プール内でメソッドが属するクラスの
インデックス u2 を示します。つまり、00 2b->43: name_and_type_index を表し、
メソッドの名前と型のインデックス

#1 = メソッド参照 #10、#43

(2) u1 をカウントダウンします。つまり、08->8: CONSTANT_String を意味し、文字列型を意味します。

CONSTANT_String_info { u1 タグ; u2 文字列インデックス; }


カウントダウン u2
u2、つまり 00 2c->44: string_index を表します

#1 = メソッド参照 #10、#43
#2 = 文字列 #44

(3) u1 をカウントダウンします。つまり 09->9: CONSTANT_Fieldref を意味し、フィールド タイプを意味します。

CONSTANT_Fieldref_info { u1 タグ; u2 クラスインデックス; u2 名前とタイプインデックス; }



u2 と u2 をカウントダウンします
。 u2、つまり 00 0d->13: class_index を表します。
u2、つまり 00 2d->45: name_and_type_index を表します。

#1 = メソッド参照 #10.#43
#2 = 文字列 #44
#3 = フィールド参照 #13.#45

2.2 仮想マシンへのクラスファイル(クラスロード機構)

いわゆるクラスロードメカニズムは次のとおりです。

仮想マシンはクラス ファイルをメモリにロードし
、データを検証し、変換、解析、および初期化して、
仮想マシンが直接使用できる Java 型、つまり java.lang.Class を形成します。

ここに画像の説明を挿入

2.2.1 ロード

クラス ファイルを検索してインポートします
(1) クラスの完全修飾名を通じてこのクラスを定義するバイナリ バイト ストリームを取得します
(2) このバイト ストリームで表される静的ストレージ構造をメソッド領域の実行時データ構造に変換します
(3) 生成しますメソッド領域のデータへのアクセス エントリとして Java ヒープ内のこのクラスを表す java.lang.Class オブジェクト

Class オブジェクトは、クラスのデータ構造をメソッド領域にカプセル化し、Java プログラマにメソッド領域のデータ構造にアクセスするためのインターフェイスを提供します。
メソッド領域のデータへのアクセス エントリとして、Java ヒープ内のこのクラスを表す java.lang.Class オブジェクトを生成します。

ここに画像の説明を挿入

2.2.2 リンク

2.2.2.1 検証

ロードされたクラスが正しいことを確認する

ファイル形式の検証
メタデータの検証
バイトコードの検証
シンボル参照の検証

2.2.2.2 準備する

クラスの静的変数にメモリを割り当て、それらをデフォルト値に初期化します。

ここに画像の説明を挿入

public class Demo1 {
    
    
	private static int i;
	public static void main(String[] args) {
    
    
		// 正常打印出0,因为静态变量i在准备阶段会有默认值0
		System.out.println(i);
	}
}
public class Demo2 {
    
    
	public static void main(String[] args) {
    
    
		// 编译通不过,因为局部变量没有赋值不能被使用
		int i;
		System.out.println(i);
	}
}

2.2.2.3 解決

クラス内のシンボリック参照を直接参照に変換する

シンボル参照は、ターゲットを説明する一連のシンボルであり、任意のリテラルを使用できます。
直接参照は、ターゲットを直接指すポインタ、相対オフセット、またはターゲットを間接的に特定するハンドルです。

解析フェーズは、仮想マシンが定数プール内のシンボリック参照を直接参照に置き換えるプロセスです。
解析アクションは主に、クラスまたはインターフェイス、フィールド、クラスメソッド、インターフェイスメソッド、メソッドタイプ、メソッドハンドル、呼び出し修飾子の 7 種類のシンボル参照に対して実行されます。

2.2.3 初期化

クラスの静的変数と静的コード ブロックに対して初期化操作を実行します。
ここに画像の説明を挿入

2.2.4 クラスローダ ClassLoader

ロード (Load) ステージのステップ (1): クラスの完全修飾名で定義されたバイナリ バイト ストリームを取得します。これは、クラス ローダーの助けを借りて完了する必要があります。名前が示すように、これが使用されます。をクリックしてクラスファイルをロードします。

2.2.4.1 分類

ここに画像の説明を挿入

2.2.4.2 図

ここに画像の説明を挿入

public class Demo3 {
    
    
	public static void main(String[] args) {
    
    
	// App ClassLoader
	System.out.println(new Worker().getClass().getClassLoader());
	// Ext ClassLoader
	System.out.println(new
	Worker().getClass().getClassLoader().getParent());
	// Bootstrap ClassLoader
	System.out.println(new
	Worker().getClass().getClassLoader().getParent().getParent());
	System.out.println(new String().getClass().getClassLoader());
	}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3a71f4dd
null
null

2.2.4.3 ローディング原則[親の委任]

(1) 特定のクラスがロードされているかどうかを確認します。
下から上に、Custom ClassLoader から BootStrap ClassLoader まで階層ごとに確認します。特定の ClassLoader がロードされている限り、そのクラスはロードされているとみなされ、これが確実にロードされていることが確認されます。クラスはすべての ClassLoader によって 1 回だけロードされます。

(2) ロードの順序は
トップダウンです。つまり、上位層はこのクラスを層ごとにロードしようとします。
ここに画像の説明を挿入

2.2.4.4 親の委任の解除

(1)トムキャット
ここに画像の説明を挿入

(2) SPI機構
(3) OSGi

2.3 実行時データ領域

ロードフェーズのステップ(2)と(3)では、実行時データ、ヒープ、メソッド領域などの名詞があることがわかります (2)
このバイトストリームで表される静的記憶構造を実行時データに変換しますメソッド領域の構造
( 3) メソッド領域のデータへのアクセスエントリとして、このクラスを表す java.lang.Class オブジェクトを Java ヒープに生成します。端的に言えば、
クラスローダによってクラスファイルがロードされた後です。 、クラスの内容 (変数、定数、メソッド、オブジェクト、その他のデータなど) は移動先、つまり保存される場所が必要であり、その保存場所には JVM 内に対応するスペースが必要です)

2.3.1 公式サイトの概要

公式ウェブサイト: https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

Java 仮想マシンは、プログラムの実行中に使用されるさまざまなランタイム データ領域を定義します。これらのデータ領域の一部は Java 仮想マシンの起動時に作成され、Java 仮想マシンが終了するときにのみ破棄されます。他のデータ領域はスレッドごとです。スレッドごとのデータ領域は、スレッドの作成時に作成され、スレッドの終了時に破棄されます。

2.3.2 図

各実行時定数プールは、Java 仮想マシンのメソッド領域 (§2.5.4) から割り当てられます。

ここに画像の説明を挿入

2.3.3 初期の理解

2.3.3.1 メソッド領域

(1) メソッド領域は、仮想マシン起動時に作成される各スレッドが共有するメモリ領域です。

Java 仮想マシンには、すべての Java 仮想マシン スレッド間で共有されるメソッド領域があります。メソッド領域は、仮想マシンの起動時に作成されます。

(2) Java 仮想マシンの仕様ではメソッド領域をヒープの論理部分として説明していますが、Java ヒープと区別することを目的とした非ヒープ (非ヒープ) と呼ばれる別名があります。

メソッド領域は論理的にはヒープの一部ですが、…

(3) 仮想マシンがロードしたクラス情報、定数、静的変数、リアルタイムコンパイラでコンパイルされたコードなどのデータを格納するために使用されます。

これには、実行時定数プール、フィールドと
メソッドのデータ、およびクラスとインスタンスの初期化およびインターフェイスの初期化で使用される特別なメソッド (§2.9) を含むメソッドとコンストラクターのコードなどのクラスごとの構造が格納されます

(4) メソッド領域がメモリ割り当て要件を満たさない場合、OutOfMemoryError 例外がスローされます。

メソッド領域内のメモリを割り当て要求を満たすために利用できるようにできない場合、Java 仮想マシンは OutOfMemoryError をスローします。

ここに画像の説明を挿入
注目すべき

JVM ランタイム データ領域は仕様であり、実際の実装は
JDK 8 では Metaspace、JDK6 または 7 では Perm Space です。

2.3.3.2 ヒープ (ヒープ)

(1) Java ヒープは、Java 仮想マシンによって管理される最大のメモリ部分で、仮想マシンの起動時に作成され、すべてのスレッドによって共有されます。
(2) Java オブジェクトのインスタンスと配列はヒープ上に配置されます。

Java 仮想マシンには、すべての Java 仮想マシン スレッド間で共有されるヒープがあります

ヒープは、すべてのクラスインスタンスと配列のメモリが割り当てられるランタイム データ領域です。
ヒープは仮想マシンの起動時に作成されます。

このとき、読み込みフェーズの 3 番目のステップを振り返り、メソッド領域内のこれらのデータへのアクセス エントリとして、Java ヒープ内のこのクラスを表す java.lang.Class オブジェクトを生成します。

この時、読み込み中の(1)(2)(3)の絵を変更することができます。
ここに画像の説明を挿入

2.3.3.3 Java仮想マシンスタック(仮想マシンスタック)

(1) 仮想マシンスタックとは、スレッドが実行される領域であり、スレッド内のメソッドの呼び出し状態を保存します。つまり、Java スレッドの実行状態は仮想マシン スタックによって保存されるため、仮想マシン スタックはスレッド プライベートかつ一意であり、スレッドの作成と同時に作成される必要があります。

各 Java 仮想マシン スレッドには、スレッドと同時に作成されるプライベート Java 仮想マシン スタックがあります。

(2) スレッドによって実行される各メソッドはスタック内のスタック フレームです。つまり、各メソッドはスタック フレームに対応します。

メソッドが呼び出されると、スタック フレームがスタックにプッシュされ、メソッド呼び出しが完了すると、スタック フレームがスタックからポップされます。

Java 仮想マシン スタックにはフレームが格納されます (§2.6)。

メソッドが呼び出されるたびに、新しいフレームが作成されます。フレームは、
メソッドの呼び出しが完了すると破棄されます。

グラフィカルスタックとスタックフレーム

void a(){
    
    
 b();
}
void b(){
    
    
 c();
}
void c(){
    
    
}

ここに画像の説明を挿入

スタックフレーム
公式Webサイト: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6

スタック フレーム: 各スタック フレームは呼び出されたメソッドに対応しており、メソッドの実行スペースとして理解できます。
各スタック フレームには、ローカル変数テーブル (Local Variables)、オペランド スタック (Operand Stack)、実行時定数プールへの参照 (Areference to the run-time constant pool)、メソッド戻りアドレス (Return Address)、および追加情報が含まれます。

ローカル変数テーブル: メソッドで定義されたローカル変数とメソッドのパラメータがこのテーブルに格納されます。
ローカル変数テーブルの変数は直接使用できません。使用する必要がある場合は、オペランド スタックにロードする必要があります関連する命令を通じて使用され、オペランドとして使用されます。

オペランド スタック: オペランドをプッシュおよびポップの形式で格納します

動的リンク: 各スタック フレームには、実行時定数プール内でスタック フレームが属するメソッドへの参照が含まれています。この参照は、メソッド呼び出し中に動的リンク (Dynamic Linking) をサポートするために保持されます。

メソッド戻りアドレス: メソッドの実行が開始されると、終了する方法は 2 つだけあり、1 つはメソッドから返されたバイトコード命令に遭遇する方法、もう 1 つは例外が発生し、例外がメソッド本体で処理されない方法です。

ここに画像の説明を挿入

バイトコード命令と組み合わせてスタック フレームを理解する
javap -c Person.class > Person.txt

Compiled from "Person.java"
class Person {
    
    
...
public static int calc(int, int);
Code:
0: iconst_3 //将int类型常量3压入[操作数栈]
1: istore_0 //将int类型值存入[局部变量0]
2: iload_0 //从[局部变量0]中装载int类型值入栈
3: iload_1 //从[局部变量1]中装载int类型值入栈
4: iadd //将栈顶元素弹出栈,执行int类型的加法,结果入栈
5: istore_2 //将栈顶int类型值保存到[局部变量2]中
6: iload_2 //从[局部变量2]中装载int类型值入栈
7: ireturn //从方法中返回int类型的数据
...
}

思考: インデックスの値は 0 か 1 か

クラス メソッドの呼び出しでは、すべてのパラメーターはローカル変数 0 から始まる連続したローカル変数に渡されます。インスタンス メソッドの呼び出しでは、インスタンス メソッドが呼び出されるオブジェクトへの参照を渡すためにローカル変数 0 が常に使用されます (これは Java の場合です)。プログラミング言語)。その後、パラメータはローカル変数 1 から始まる連続したローカル変数に渡されます。

ここに画像の説明を挿入

2.3.3.4 pc レジスタ (プログラム カウンタ)

JVM プロセスでは複数のスレッドが実行されており、スレッド内のコンテンツが実行権限を持つことができるかどうかは CPU スケジューリングに基づいていることは誰もが知っています。
スレッド A がどこかで実行していて、突然 CPU の実行権を失い、スレッド B に切り替わった場合、スレッド A が
再び CPU の実行権を獲得したとき、どうすれば実行を継続できるでしょうか。これは、スレッドが実行される場所を記録するためにスレッド内で変数を維持する必要があるためです。

スレッドが Java メソッドを実行している場合、カウンタは実行されている仮想マシンのバイトコード命令のアドレスを記録します。
ネイティブ メソッドが実行されている場合、カウンタは空です。

Java 仮想マシンは、一度に多くのスレッドの実行をサポートできます (JLS §17)。各 Java 仮想マシン スレッドには、独自の PC (プログラム カウンタ) レジスタがあります。どの時点でも、各 Java 仮想マシン スレッドは 1 つのメソッド、つまりそのスレッドの現在のメソッド (§2.6) のコードを実行しています。そのメソッドがネイティブでない場合、pc レジスタには現在実行されている Java 仮想マシン命令のアドレスが含まれます
スレッドによって現在実行されているメソッドがネイティブの場合、Java 仮想マシンの PC レジスタの値は未定義です。Java 仮想マシンの PC レジスタは、特定のプラットフォーム上の returnAddress またはネイティブ ポインタを保持するのに十分な幅を持っています。

2.3.3.5 ネイティブ メソッド スタック (ローカル メソッド スタック)

現在のスレッドによって実行されるメソッドがネイティブ タイプの場合、これらのメソッドはネイティブ メソッド スタックで実行されます。
では、Java メソッドの実行時にネイティブ メソッドが呼び出された場合はどうなるでしょうか?
ここに画像の説明を挿入

2.3.4 トス

2.3.4.1 ヒープへのスタック ポイント

スタック フレームに変数があり、その型が Object obj=new Object() などの参照型である場合、それはヒープ内のオブジェクトを指すスタック内の典型的な要素です。
ここに画像の説明を挿入

2.3.4.2 メソッド領域がヒープを指す

静的変数、定数、その他のデータはメソッド領域に格納されます。この場合、典型的なメソッド領域の要素はヒープ内のオブジェクトを指します。

private static Object obj=new Object();

ここに画像の説明を挿入

2.3.4.3 ヒープはメソッド領域を指します

ヒープは引き続きメソッド領域を指すことができますか?
メソッド領域にはクラス情報が含まれ、ヒープ内にはオブジェクトが存在することに注意してください。では、どのクラスがオブジェクトを作成したかをどのようにして知ることができるのでしょうか?
ここに画像の説明を挿入

考察:
オブジェクトはどのクラスから作成されたかをどのようにして知るのでしょうか? どうやって記録するのですか?これには、Java オブジェクトの固有の情報を理解する必要があります。

2.3.4.4 Java オブジェクト メモリ モデル

Java オブジェクトは、メモリ内の 3 つの部分 (オブジェクト ヘッダー、インスタンス データ、およびアライメント パディング) で構成されます。
ここに画像の説明を挿入

2.4 JVMメモリモデル

2.4.1 および実行時データ領域

上記ではランタイム データ領域について詳しく説明しています。実際、データ ストレージの焦点はヒープとメソッド領域 (非ヒープ) であるため、メモリ設計もこれら 2 つの領域に焦点を当てています (これら 2 つの領域は共有されていることに注意してください)スレッドによって)。
仮想マシン スタック、ネイティブ メソッド スタック、およびプログラム カウンターはすべてスレッド プライベートです。
JVM ランタイム データ領域は仕様であり、JVM メモリ モデルは仕様の実装であることが理解できます。

2.4.2 グラフィック表示

1 つは非ヒープ領域、もう 1 つはヒープ領域です。ヒープ領域は
2 つのブロックに分割され、1 つは Old 領域、もう 1 つは Young 領域です。Young 領域は
2 つのブロックに分割され、1 つは Survivor です。エリア (S0+S1)、もう 1 つはエデンエリア
S0 と S1 は同じ大きく、From と To とも呼ばれます
ここに画像の説明を挿入

2.4.3 オブジェクトの作成プロセス

一般に、新しく作成されたオブジェクトは Eden 領域に割り当てられ、一部の特別なラージ オブジェクトは Old 領域に直接割り当てられます。

私は普通の Java オブジェクトです。私はエデン地区で生まれました。また、私によく似た弟もエデン地区で見かけ、私たちはエデン地区でかなり長い間遊びました。ある日、エデンエリアに人が多すぎたので、強制的にサバイバーエリアの「フロム」エリアに行くことになりました。サバイバーエリアに行って以来、ドリフトを始めました。時々「フロム」エリアで当時、私はサバイバーの「To」エリアにいて、決まった場所に住んでいませんでした。私が18歳になるまで、父は私が大人になったから社会に飛び込む時期だと言いました。そこで私は旧世代に行きました。旧世代にはたくさんの人がいて、みんなかなり年配です。
ここに画像の説明を挿入

2.4.4 よくある質問

マイナー/メジャー/フル GC を理解する方法

マイナー GC: 新世代
メジャー GC: 旧世代
フル GC: 新世代 + 旧世代

なぜサバイバーエリアが必要なのですか?それはエデンだけではないでしょうか?

Survivor が存在しない場合、Eden エリアでマイナー GC が実行されるたびに、生き残ったオブジェクトが古い時代に送られます。
このようにして、古い年齢はすぐに埋められ、メジャー GC がトリガーされます (メジャー GC には通常、マイナー GC が伴い、フル GC のトリガーともみなされるため)。
旧世代のメモリ空間は新世代のメモリ空間よりもはるかに大きく、フル GC の実行にはマイナー GC よりもはるかに長い時間がかかります。
実行時間が長いことのデメリットは何ですか? フル GC を頻繁に実行すると時間がかかり、大規模なプログラムの実行速度と応答速度に影響します。
そうすると、古い世代のスペースを増やしたり減らしたりすることができます。
古い世代のスペースが増加すると、より多くの存続オブジェクトが古い世代を埋めることができます。Full GC の頻度は減りますが、古い時代の領域が増えると、一度 Full GC が発生すると実行に時間がかかります。
古い世代の領域が減少すると、Full GC にかかる時間は短縮されますが、すぐに古い世代が生き残ったオブジェクトでいっぱいになり、Full GC の頻度が増加します。
したがって、Survivor の存在意義は、古い世代に送信されるオブジェクトの数を減らし、フル GC の発生を減らすことです。Survivor の事前スクリーニングにより、16 回のマイナー GC 後に新しい世代に生き残ることができるオブジェクトのみが送信されることが保証されます。新しい世代へ、古い時代へ。

なぜ 2 つの Survivor ゾーンが必要なのでしょうか?

最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置
Survivor区。假设现在只有一个Survivor,我们来模拟一下流程:
刚刚新建的对象在Eden,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor
区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,EdenSurvivor各有一些
存活对象,如果此时把Eden区的存活对象硬放到Survivor,很明显这两部分对象所占有的内存是不连续的,
也就导致了内存碎片化。
永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。

新世代ではなぜ Eden:S1:S2 が 8:1:1 になっているのですか?

新世代で使用可能なメモリ: コピー アルゴリズムで保証されるメモリは 9:1、
使用可能なメモリの Eden:S1 領域は 8:1、
つまり、新世代では Eden:S1:S2 = 8:1:1最新の
商用仮想マシンは、この収集アルゴリズムを使用して新世代をリサイクルしており、IBM の特別な調査では、新世代のオブジェクトの約 98% が
「生きて死ぬ」ことが示されています。

ヒープメモリはスレッドが共有する領域なのでしょうか?


デフォルトでは、JVM はオブジェクトの割り当てを高速化するために各スレッドの Eden 上に TLAB (正式名: Thread Local Allocation Buffer)と呼ばれるバッファ領域を開きます。
オブジェクトは最初に TLAB 上に割り当てられますが、TLAB スペースは通常比較的小さいため、オブジェクトが比較的大きい場合でも、共有領域に割り当てられます。

2.4.5 経験と検証

2.4.5.1 VisualVMの使用

Visualgc プラグインのダウンロード リンク: https://visualvm.github.io/pluginscenters.html
対応する JDK バージョンのリンクを選択します—>ツール—>Visual GC
上記のリンクで適切なものが見つからない場合は、オンラインで対応するバージョンをダウンロードすることもできます

2.4.5.2 ヒープメモリのオーバーフロー

コード

@RestController
public class HeapController {
    
    
	List<Person> list=new ArrayList<Person>();
	@GetMapping("/heap")
	public String heap(){
    
    
		while(true){
    
    
			list.add(new Person());
		}
	}
}


-Xmx20M -Xms20M実行結果などのパラメータを忘れずに設定してください
。 http://localhost:8080/heap にアクセスしてください。

スレッド「http-nio-8080-exec-2」java.lang.OutOfMemoryError: GC オーバーヘッド制限を超えた例外

2.4.5.3 メソッド領域のメモリオーバーフロー


たとえば、クラス情報asm 依存関係とクラス コードをメソッド領域に追加します。

<dependency>
	<groupId>asm</groupId>
	<artifactId>asm</artifactId>
	<version>3.3.1</version>
</dependency>
public class MyMetaspace extends ClassLoader {
    
    
	public static List<Class<?>> createClasses() {
    
    
	List<Class<?>> classes = new ArrayList<Class<?>>();
	for (int i = 0; i < 10000000; ++i) {
    
    
		ClassWriter cw = new ClassWriter(0);
		cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
		"java/lang/Object", null);
		MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
		"()V", null, null);
		mw.visitVarInsn(Opcodes.ALOAD, 0);
		mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
		"<init>", "()V");
		mw.visitInsn(Opcodes.RETURN);
		mw.visitMaxs(1, 1);
		mw.visitEnd();
		Metaspace test = new Metaspace();
		byte[] code = cw.toByteArray();
		Class<?> exampleClass = test.defineClass("Class" + i, code, 0,
		code.length);
		classes.add(exampleClass);
		}
	return classes;
	}
}

コード

@RestController
public class NonHeapController {
    
    
	List<Class<?>> list=new ArrayList<Class<?>>();
	@GetMapping("/nonheap")
	public String nonheap(){
    
    
		while(true){
    
    
			list.addAll(MyMetaspace.createClasses());
		}
	}
}

メタスペースのサイズを設定します (例: -XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M)。
実行結果は
->http://localhost:8080/nonheap にアクセスしてください。

java.lang.OutOfMemoryError:
java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_191] のメタスペース
java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_191]

2.4.5.4 仮想マシンスタック

コードのデモStackOverFlow

public class StackDemo {
    
    
	public static long count=0;
	public static void method(long i){
    
    
	System.out.println(count++);
		method(i);
	}
	public static void main(String[] args) {
    
    
		method(1);
	}
}

演算結果
ここに画像の説明を挿入

説明する

スタック スペースは、メソッドの再帰呼び出しを行うときにスタック フレーム (スタック フレーム) にプッシュするために使用されます。そのため、再帰呼び出しが深すぎると、スタック領域が不足し、StackOverflow エラーが発生する可能性があります。
-Xss128k: 各スレッドのスタック サイズを設定します。JDK 5 以降、各スレッドのスタック サイズは 1M ですが、以前は各スレッドのスタック サイズは 256K でした。

アプリケーションのスレッドが必要とするメモリ サイズに応じて調整します。同じ物理メモリの場合、この値を減らすと、より多くのスレッドが生成される可能性があります。ただし、OSには依然としてプロセス内のスレッド数の制限があり、無限にスレッドを生成することはできず、経験値は3000~5000程度となります。

スレッド スタックのサイズは諸刃の剣です。設定が小さすぎるとスタック オーバーフローが発生する可能性があり、特にスレッド内に再帰や大きなループがある場合は、オーバーフローの可能性が高くなります。値を設定しすぎると、スタック オーバーフローが発生する可能性が高くなります。大きい場合はスタック作成数に影響し、マルチスレッドアプリケーションの場合はメモリオーバーフローエラーが発生します。

2.5 ガベージコレクト(ガベージコレクション)

ヒープメモリにはガベージコレクションがあり、Young領域ではMinor GC、Old領域ではMajor GC、Young領域とOld領域ではFull GCがあると以前述べました。
しかし、オブジェクトの場合、それがゴミであるとどうやって判断できるのでしょうか? リサイクルする必要がありますか? どうやってリサイクルするのですか?これらの問題についてはまだ詳しく調査する必要があります。
Java はメモリ管理とガベージ コレクションを自動的に実行するため、ガベージ コレクションのすべての側面を理解していないと、問題が発生したときにトラブルシューティングして解決する
こと処理する分類して判断し、
使用中のオブジェクトと使用されなくなったオブジェクトを見つけて、使用されないオブジェクトをヒープから削除します。

2.5.1 オブジェクトがガベージかどうかを判断するにはどうすればよいですか?

ガベージ コレクションを実行するには、まずどのような種類のオブジェクトがガベージであるかを知る必要があります。

2.5.1.1 参照カウント

オブジェクトの場合、アプリケーションがオブジェクトへの参照を保持している限り、そのオブジェクトはガベージではないことを意味し、オブジェクトへのポインタ参照がまったくない場合、そのオブジェクトはガベージです。
欠点: AB が相互参照を保持している場合、それは決してリサイクルされません。

2.5.1.2 アクセシビリティ分析

GC ルートのオブジェクトを通して、オブジェクトが到達可能かどうかを確認するために下を向き始めます。
ここに画像の説明を挿入

GC ルートとして使用可能: クラスローダー、スレッド、仮想マシンスタックのローカル変数テーブル、静的メンバー、定数参照、ローカル
メソッドスタックの変数など。

仮想マシン スタック (スタック フレーム内のローカル変数テーブル) で参照されるオブジェクト。
メソッド領域のクラス静的プロパティによって参照されるオブジェクト。
メソッド領域の定数によって参照されるオブジェクト。
ローカル メソッド スタック内の JNI (つまり、一般的なネイティブ メソッド) によって参照されるオブジェクト。

2.5.2 ガベージ コレクションはいつ行われますか?

GC は JVM システム環境に応じて JVM によって自動的に完了するため、タイミングは不確実です。
もちろん、System.gc() メソッドを呼び出して JVM にガベージ コレクションを実行するように通知するなど、ガベージ コレクションを手動で実行することはできますが、いつ
実行するかを制御することはできません。つまり、 System.gc() はリサイクルするよう通知するだけであり、いつリサイクルするかは JVM によって決定されます
ただし、GC によって消費されるリソースが比較的大きいため、このメソッドを手動で呼び出すことはお勧めできません。

(1) Eden領域またはS領域が足りない場合
(2) 旧世代領域が足りない場合
(3) メソッド領域が足りない場合
(4) System.gc()

2.5.3 ガベージコレクションアルゴリズム

ゴミと判断できたら、次に考えるべきはリサイクルです。対応するアルゴリズムが必要です。
以下に一般的なガベージ コレクション アルゴリズムを紹介します。
2.5.3.1 マークスイープ (Mark-スイープ)
マーク
メモリ内で再利用が必要なオブジェクトを見つけ出し、マークアウトします。
このとき、ヒープ内のすべてのオブジェクトがスキャンされて、再利用が必要なオブジェクトが決定されます。 、時間がかかります
ここに画像の説明を挿入

クリア
リサイクルするようにマークされたオブジェクトをクリアし、対応するメモリ領域を解放します。
ここに画像の説明を挿入

欠点がある

マークがクリアされると、大量の不連続なメモリ フラグメントが生成されます。領域の断片化が多すぎると、プログラムが
将来大きなオブジェクトを割り当てる必要があるときに、十分な連続メモリを見つけることができず、別のガベージ コレクション アクションをトリガーする必要がある可能性があります。あらかじめ。
(1) マークとクリアの 2 つのプロセスは時間がかかり、非効率です
(2) 不連続なメモリ断片が大量に生成されます 領域の断片化が多すぎると、プログラムの実行中に大きなオブジェクトを割り当てることができなくなる可能性があります。十分な連続メモリが必要なため、別のガベージ コレクション アクションを早めにトリガーする必要があります。

2.5.3.2 マークコピー

次の図に示すように、メモリを 2 つの等しい領域に分割し、一度にそのうちの 1 つだけを使用します。
ここに画像の説明を挿入

一つのメモリが使い果たされたら、残ったオブジェクトを別のメモリにコピーし、使用済みのメモリ領域を一括クリアします。
ここに画像の説明を挿入

短所: スペースの使用率が低下します。

2.5.3.3 マークコンパクト

コピー収集アルゴリズムは、オブジェクトの生存率が高い場合、より多くのコピー操作を実行するため、効率が低くなります。さらに重要なのは、
スペースの 50% を無駄にしたくない場合は、使用されているメモリ内のすべてのオブジェクトが
100% 生きているという極端な状況に対処するために、割り当てを保証するための追加のスペースが必要であるため、一般に古い世代ではこれを行うことができません。このアルゴリズムを直接選択してください。

マーキング プロセスは「マーククリア」アルゴリズムと同じですが、リサイクル可能なオブジェクトを直接クリーンアップする代わりに、後続のステップで、残っているすべてのオブジェクトを一方の端に移動し、端の境界の外側にあるメモリを直接クリーンアップします。

実際、「コピーアルゴリズム」と比較して、上記のプロセスには「予約領域」がありません。

ここに画像の説明を挿入

生き残ったすべてのオブジェクトを一方の端に移動させ、境界を越えてメモリをクリーンアップします。
ここに画像の説明を挿入

2.5.4 世代別収集アルゴリズム

上で 3 つのガベージ コレクション アルゴリズムを紹介しましたが、ヒープ メモリではどれを使用すべきでしょうか?

ヤング領域:コピーアルゴリズム(オブジェクト割り当て後のライフサイクルは比較的短く、ヤング領域のコピー効率は比較的高い) オールド領域
:マーククリアまたはマークフィニッシング(オールド領域でのオブジェクトの生存時間)この領域は比較的長いため、コピーする必要はありません。マークを付けてクリーンアップすることをお勧めします)

2.5.5 ガベージコレクター

コレクション アルゴリズムがメモリ回復の方法論である場合、ガベージ コレクターはメモリ回復の特定の実装です。
ここに画像の説明を挿入

2.5.5.1 シリアル

シリアル コレクターは、開発の歴史が最も長く、最も基本的なコレクターであり、(JDK1.3.1 より前は) 新世代の仮想マシンが収集する唯一の選択肢でした。
これはシングルスレッド コレクターです。これは、ガベージ コレクションを完了するために 1 つの CPU または 1 つのコレクション スレッドのみを使用することを意味するだけでなく、さらに重要なことに、ガベージ コレクション中に他のスレッドを一時停止する必要があることを意味します。

利点: シンプルかつ効率的で、単一スレッドの収集効率が高い
欠点: 収集プロセスはすべてのスレッドを一時停止する必要がある
アルゴリズム: コピー アルゴリズム
適用範囲: 新世代
アプリケーション: クライアント モードのデフォルトの新世代コレクタ

ここに画像の説明を挿入

2.5.5.2 古いシリアル

Serial Old コレクターは、Serial コレクターの旧バージョンであり、シングルスレッド コレクターでもあります。違いは、「マークソート アルゴリズム」を使用することであり、操作プロセスは Serial コレクターと同じです
。 。
ここに画像の説明を挿入

2.5.5.3 ParNew

このコレクターは、シリアル コレクターのマルチスレッド バージョンとして理解できます。

利点: 複数の CPU がある場合、シリアルよりも効率的です。
短所: 収集プロセスによりすべてのアプリケーション スレッドが一時停止され、単一の CPU を使用する場合は効率がシリアルよりも悪くなります。
アルゴリズム: コピー アルゴリズム
適用範囲: 新世代
アプリケーション: サーバー モードで実行されている仮想マシンで推奨される新世代のコレクター

ここに画像の説明を挿入

2.5.5.4 パラレルスカベンジ

Parallel Scavenge コレクターは新世代のコレクターです。レプリケーション アルゴリズムと並列マルチスレッド コレクターを使用するコレクターでもあります。見た目は ParNew と同じですが、Parallel Scanvenge はシステムのスループットにさらに注意を払っています。

スループット = ユーザー コードの実行時間 / (ユーザー コードの実行時間 + ガベージ コレクション時間)
たとえば、仮想マシンが合計 100 分間実行され、ガベージ コレクション時間が 1 分かかる場合、スループット = (100-1)/ 100=99%。
スループットが大きいほど、ガベージ コレクション時間が短くなり、ユーザー コードは CPU リソースを最大限に活用してプログラムの計算タスクをできるだけ早く完了できます。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。

2.5.5.5 古いパラレル

Parallel Old コレクターは、Parallel Scavenge コレクターの古いバージョンであり、ガベージ コレクションにマルチスレッドおよびマークソート アルゴリズムを使用し、システムのスループットにもより注意を払っています。

2.5.4.6 CMS

公式 Web サイト: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_スイープ_cms_collector

CMS (同時マーク スイープ) コレクターは、最短の回復休止時間を取得することを目的としたコレクターです。
「マーククリアアルゴリズム」を採用し、全工程を4ステップに分けて実行

(1) 初期マーク CMS 初期マーク GC ルートをマークしてオブジェクトを直接関連付けます。トレースなし、速度は非常に高速です (2)
コンカレント マーク GC ルート トレース用の CMS コンカレント マーク
(3) CMS リマークを再マーク コンカレント マークの内容を変更しますユーザー プログラムの変更による
(4) CMS 同時スイープの同時クリアにより、到達不能なオブジェクトのリカバリ領域がクリアされ、同時に新しいガベージが生成されます。これは、次のクリーニング用のフローティング ガベージと呼ばれます。

コレクタ スレッドは、同時マーキングおよび同時クリアのプロセス全体でユーザー スレッドと連携できるため、一般に、
CMS コレクタのメモリ回復プロセスはユーザー スレッドと同時に実行されます。
ここに画像の説明を挿入

2.5.5.7 G1(ガベージファースト)

公式ウェブサイト: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection

G1 コレクターを使用する場合、Java ヒープのメモリ レイアウトは他のコレクターとは大きく異なり、Java ヒープ全体が
同じサイズの複数の独立した領域 (リージョン) に分割されますが、新しい世代と古い世代は存在します。新しい世代と古い世代は
もはや物理的に分離されており、リージョンの一部の集合です (連続している必要はありません)。

各リージョンのサイズは同じで、1M から 32M までの値を指定できますが、2 の n 乗であることが保証されている必要があります。
オブジェクトが大きすぎてリージョンに収まらない場合は [領域の 50% 以上]リージョン サイズ] を選択すると、直接 H に入れて
リージョン サイズを設定します。 -XX:G1HeapRegionSize=M
いわゆるガベージ ファーストとは、実際には最もゴミの多いリージョン領域を優先することです。

(1) 世代別コレクション (世代の概念を保持)
(2) スペース統合 (全体として「マークソート」アルゴリズムに属し、スペースの断片化を引き起こしません) (3) 予測可能な一時停止 (
CMS よりも高度)重要なのは、ユーザーが長さ M ミリ秒の時間セグメントを明確に指定でき、ガベージ コレクションに費やされる時間は N ミリ秒を超えてはいけないということです)

ここに画像の説明を挿入

作業プロセスは次のステップに分けることができます。
初期マーキング (Initial Marking) では、以下の GC ルートに関連付けることができるオブジェクトにマークを付けます。TAMS の値を変更するには、ユーザー
スレッドを一時停止する必要があります。) GC ルートからの到達可能性分析を実行して、生き残ったオブジェクトがユーザー スレッドと同時に実行されていることを確認します
。最終マーキングは、同時マーキング フェーズ中のユーザー プログラムの同時実行によって変更されたデータを修正します。ユーザー スレッドは一時停止する必要があります。各リージョンの回復値をスクリーンして
リサイクル (ライブ データのカウントと退避)コストで並べ替え、ユーザーの予想される GC 一時停止時間に応じて回復計画を作成
ここに画像の説明を挿入

2.5.5.8 ZGC

公式ウェブサイト: https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0

JDK11で導入された新しいZGCコレクターは、物理的にも論理的にも、ZGCには新旧の概念がなくなり、ページごとに分割され、GC操作が実行されるとページが圧縮されるため
、断片化の問題はありません
。64 ビット Linux で使用できますが、現時点ではまだ比較的まれに使用されています。

(1) 10ms 以内の休止時間要件を満たすことができる
(2) TB レベルのメモリをサポート
(3) ヒープメモリが大きくなっても、休止時間は 10ms 以内にとどまる

ここに画像の説明を挿入

2.5.5.9 ガベージ コレクターの分類

Serial Collector -> Serial および Serial Old は
1 つのガベージ コレクション スレッドによってのみ実行でき、ユーザー スレッドは一時停止されます。
比較的小さなメモリを備えた組み込みデバイスに適しています。

パラレル コレクター [スループット優先] - > パラレル Scanvenge、パラレル オールド
複数のガベージ コレクション スレッドが並行して動作しますが、この時点ではユーザー スレッドはまだ待機状態です。

初期マーキング (Initial Marking) は、
GC ルートに関連付けることができる次のオブジェクトをマークします。TAMS の値を変更するには、ユーザー スレッドを一時停止する必要があります。

同時マーキングは、
GC ルートからの到達可能性分析を実行し、生き残ったオブジェクトを見つけて、ユーザー
スレッドと同時に実行します。

最終マーキング (ファイナル マーキング) は、
同時マーキング フェーズ中にユーザー プログラムの同時実行によって変更されたデータを修正し、ユーザー スレッドを一時停止する必要があります。

スクリーニングとリカバリ (ライブデータのカウントと退避) は、
各リージョンのリカバリ値とコストを分類し、ユーザーの予想される GC 一時停止時間に基づいてリカバリ計画を策定するため、
科学計算やバックグラウンド処理などのインタラクティブなシナリオに適しています。

コンカレント コレクター[一時停止時間優先] -> CMS、G1
ユーザー スレッド、ガベージ コレクション スレッドは同時に実行され (ただし、必ずしも並列で実行される必要はなく、交互に実行される場合もあります)、ガベージ コレクション スレッドは実行中にユーザー スレッドを一時停止しません。走る。
Web など、相対的な時間を必要とするシナリオに適用できます。

2.5.5.10 よくある質問

スループットと一時停止時間
一時停止時間 -> ガベージ コレクタ ガベージ コレクション終了 アプリケーション実行応答 スループット
-> ユーザー コードの実行時間 / (ユーザー コードの実行時間 + ガベージ コレクション時間)

一時停止時間が短いほど、ユーザーと対話する必要があるプログラムに適しています。応答速度が優れていると、ユーザー エクスペリエンスが向上します。スループットが高いと、CPU 時間を効率的に使用して、
プログラムの計算タスクをできるだけ早く完了できます。主に適しています。あまり多くの手間をかけずにバックグラウンドで計算できる 複数のインタラクティブなタスク。

要約: これら 2 つの指標は、ガベージ コレクターの利点を評価するための基準でもあります。

適切なガベージ コレクターを選択する方法
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28

ヒープのサイズ調整を優先し、サーバー自身に選択させます。
メモリが 100M 未満の場合は、シリアル コレクターを使用します
。シングル コアで一時停止時間の要件がない場合は、シリアルまたは JVM を使用します
。一時停止の場合は、シリアル コレクターを使用します。時間が 1 秒を超えることは許可されています。パラレルまたは JVM を選択してください。自分で選択してください。
応答時間が最も重要であり、1 秒を超えることができない場合は、コンカレント コレクターを使用してください。

G1 コレクションでは
、JDK 7 が使用され始め、JDK 8 は非常に成熟しており、JDK 9 のデフォルトのガベージ コレクターは新世代と古い世代に適しています。
G1コレクターは使用されていますか?

(1) ヒープの 50% 以上が生き残ったオブジェクトによって占有されている
(2) オブジェクトの割り当てと昇格の速度は大きく異なる
(3) ガベージ コレクション時間が比較的長い

G1 の RSet の
正式名はRemembered Set で、リージョン内のオブジェクトの参照関係を記録し維持します。

想像してみてください。G1 ガベージ コレクターが新しい世代でガベージ コレクション (つまりマイナー GC) を実行するとき、オブジェクトが古い世代のリージョンによって参照されている場合、現時点では新しい世代のオブジェクトをリサイクルできません。それを記録しなさい?
これはどうでしょうか。ハッシュに似た構造を使用し、キーは領域のアドレスを記録し、値はオブジェクトを参照しているコレクションを示します。これにより、古い世代のどのオブジェクトがそのオブジェクトを参照しているかがわかります。リサイクルすることはできません。

必要なガベージ コレクターを有効にする方法

(1) シリアル
-XX: +UseSerialGC
-XX: +UseSerialOldGC
(2) パラレル (スループット優先):
-XX: +UseParallelGC
-XX: +UseParallelOldGC
(3) コンカレント コレクター (応答時間優先)
-XX: + UseConcMarkSweatGC
-XX :+G1GC を使用

ここに画像の説明を挿入

3. 詳細

3.1 JVMパラメータ

3.1.1 標準パラメータ

-バージョン
-ヘルプ
-サーバー
-cp

ここに画像の説明を挿入

3.1.2 -X パラメータ

非標準パラメータ、つまり、JDK のバージョンごとに変更される可能性があります

-Xint 解釈と実行
-Xcomp は、初めて使用するときにローカル コードにコンパイルします
-Xmixed 混合モード、JVM が独自に決定します

ここに画像の説明を挿入

3.1.3 -XXパラメータ

最も使用されるパラメータのタイプ
標準化されていないパラメータで、比較的不安定で、主に JVM のチューニングとデバッグに使用されます。

a.Boolean 型の
形式: -XX:[±] + または - は、名前属性を有効または無効にすることを意味します。
例: -XX:+UseConcMarkSoupGC は、CMS タイプのガベージ コレクターを有効にすることを意味します。
-XX:+UseG1GC は、G1 タイプを有効にすることを意味しますガベージコレクター

b. 非ブール型形式: -XX= は、name 属性の値が value であることを示します
例: -XX:MaxGCPauseMillis=500
-Xms1000M は、-XX:InitialHeapSize=1000M と同等です
-Xmx1000M は、-XX:MaxHeapSize と同等です=1000M
-Xss100 など -XX:ThreadStackSize=100 の値

3.1.4 その他のパラメータ

-Xms1000M は -XX:InitialHeapSize=1000M と同等
-Xmx1000M は -XX:MaxHeapSize=1000M と
同等 -Xss100 は -XX:ThreadStackSize=100 と同等

したがって、この部分もタイプ -XX のパラメータと同等です。

3.1.5 ビューパラメータ

java -XX:+PrintFlagsFinal -version > flags.txt
ここに画像の説明を挿入
ここに画像の説明を挿入

「=」はデフォルト値を示し、「:=」はユーザーまたは JVM によって変更された値を示します。プロセスの
特定のパラメータの値を表示したい場合は、jinfo を使用できます。
一般に、パラメータを設定する必要があります。最初に現在のパラメータを確認できます。何を変更してから変更します

3.1.6 パラメータを設定する一般的な方法


Eclipse が jar パッケージを実行するときのIDEA などの開発ツールの設定: java -XX:+UseG1GC xxx.jar
tomcat などの Web コンテナをスクリプトに設定し、
jinfo (パラメータはマークのみ可能、管理可能なフラグはリアルタイムで変更可能)

3.1.7 実践と単位換算

1バイト(バイト)=8ビット(ビット)
1KB=1024バイト(バイト)
1MB=1024KB
1GB=1024MB
1TB=1024GB

(1) ヒープメモリサイズとパラメータを設定 print
-Xmx100M -Xms100M -XX:+PrintFlagsFinal
(2) +PrintFlagsFinal の値を問い合わせ
:=true
(3) ヒープメモリサイズを問い合わせ MaxHeapSize
:= 104857600
(4)
104857600( Byte)/ 1024=102400(KB)
102400(KB)/1024=100(MB)
(5) 結論
104857600はバイト単位です

3.1.8 共通パラメータの意味

ここに画像の説明を挿入
ここに画像の説明を挿入

3.2 共通コマンド

3.2.1 jps

Javaプロセスを表示する

jps コマンドは、ターゲット システム上のインストルメントされた Java HotSpot VM を一覧表示します。このコマンドは、アクセス許可を持つ JVM に関する情報のレポートに限定されます。

ここに画像の説明を挿入

3.2.2 ジン情報

(1) JVM 構成パラメータをリアルタイムで表示および調整する

jinfo コマンドは、指定された Java プロセス、コア ファイル、またはリモート デバッグ サーバーの Java 構成情報を出力します。構成情報には、Java システム プロパティと Java 仮想マシン (JVM) コマンドライン フラグが含まれます。

(2) 使用方法の表示
jinfo -flag name PID Javaプロセスのname属性の値を表示

jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID

ここに画像の説明を挿入

(3)
パラメータの変更 管理可能としてマークされたフラグのみがリアルタイムで変更できます

jinfo -flag [+|-] PID 
jinfo -flag <name>=<value> PID

(4) 値が割り当てられているいくつかのパラメータを表示します

jinfo -flags PID

ここに画像の説明を挿入

3.2.3 スタンド

(1) 仮想マシンのパフォーマンス統計の表示

jstat コマンドは、インストルメントされた Java HotSpot VM のパフォーマンス統計を表示します。ターゲット JVM は、仮想マシン識別子または vmid オプションによって識別されます。

(2) クラスロード情報の表示

jstat -class PID 1000 10 Javaプロセスのクラスロード情報を表示し、1000ミリ秒ごとに1回出力し、合計10回出力します。

ここに画像の説明を挿入

(3) ガベージコレクション情報の表示

jstat -gc PID 1000 10

ここに画像の説明を挿入

3.2.4 jスタック

(1) スレッドスタック情報の表示

jstack コマンドは、指定された Java プロセス、コア ファイル、またはリモート デバッグ サーバーの Java スレッドの Java スタック トレースを出力します。

(2) 用途

jstack PID

ここに画像の説明を挿入

(3) デッドロックケースのトラブルシューティング
DeadLockDemo

//运行主类
public class DeadLockDemo
{
    
    
    public static void main(String[] args)
    {
    
    
        DeadLock d1=new DeadLock(true);
        DeadLock d2=new DeadLock(false);
        Thread t1=new Thread(d1);
        Thread t2=new Thread(d2);
        t1.start();
        t2.start();
    }
}
//定义锁对象
class MyLock{
    
    
    public static Object obj1=new Object();
    public static Object obj2=new Object();
}
//死锁代码
class DeadLock implements Runnable{
    
    
    private boolean flag;
    DeadLock(boolean flag){
    
    
        this.flag=flag;
    }
    public void run() {
    
    
        if(flag) {
    
    
            while(true) {
    
    
                synchronized(MyLock.obj1) {
    
    
                    System.out.println(Thread.currentThread().getName()+"----if获得obj1锁");
                    synchronized(MyLock.obj2) {
    
    
                        System.out.println(Thread.currentThread().getName()+"----if获得obj2锁");
                    }
                }
            }
        }
        else {
    
    
            while(true){
    
    
                synchronized(MyLock.obj2) {
    
    
                    System.out.println(Thread.currentThread().getName()+"----否则获得obj2锁");
                    synchronized(MyLock.obj1) {
    
    
                        System.out.println(Thread.currentThread().getName()+"----否则获得obj1锁");

                    }
                }
            }
        }
    }
}

実行結果
ここに画像の説明を挿入
jstack解析
ここに画像の説明を挿入
印刷情報を最後まで引っ張って検索

画像.png

3.2.5 jmap

(1) ヒープダンプスナップショットの生成

jmap コマンドは、指定されたプロセス、コア ファイル、またはリモート デバッグ サーバーの共有オブジェクト メモリ マップまたはヒープ メモリの詳細を出力します。

(2) ヒープメモリ関連情報の出力

jmap -ヒープPID

jinfo -flag UsePSAdaptiveSurvivorSizePolicy 35352
-XX:SurvivorRatio=8

ここに画像の説明を挿入

(3) ヒープメモリ関連情報をダンプする

jmap -dump:format=b,file=heap.hprof PID

ここに画像の説明を挿入

(4) ファイルを出力するように本番ダンプを構成します。
一般に、開発中に次の 2 つの文を JVM パラメータに追加すると、メモリがオーバーフローしたときにファイルが自動的にダンプされます
-XX:+HeapDumpOnOutOfMemoryError -XX: HeapDumpPath=heap.hprof

ヒープ メモリ サイズを設定します: -Xms20M -Xmx20M
start、次に localhost:9090/heap にアクセスして、ヒープ メモリをオーバーフローさせます。

4つのパフォーマンス最適化

JVM パフォーマンスの最適化は、コード レベルと非コード レベルに分けられます。コード レベルでは、ループ ステートメントなどの最適化のためのバイトコード命令を組み合わせることができます。これにより、ループ本体から
ループに関係のないコードを抽出できるため、これらのコードをバイトコード レベルで繰り返し実行する必要がなくなります。非コード レベルでは、一般に、メモリ、GC、CPU の使用量に関して最適化できます。JVM のチューニングは長く複雑なプロセスであり、JVM 自体が多くの内部最適化操作を行っているため、多くの場合、JVM を最適化する必要がないことに注意してください。JVM の最適化については、メモリ、GC、CPU の 3 つの側面から議論できますが、チューニングを繰り返しないように注意する必要があります。




4.1 メモリ

4.1.1 メモリの割り当て

通常の状況では設定する必要はありませんが、プロモーションやフラッシュセールのシナリオの場合はどうなるでしょうか?
各マシンは 2c4G で構成されており、例として 1 秒あたり 3000 件の注文があり、プロセス全体は 60 秒かかります。
ここに画像の説明を挿入

4.1.2 メモリ不足 (OOM)

一般に、理由は 2 つあります。
(1) 同時実行性が高い場合
(2) メモリ リークによりメモリ オーバーフローが発生する

4.1.2.1 大規模同時実行 [Seckill]

ブラウザ キャッシュ、ローカル キャッシュ、検証コード
CDN 静的リソース サーバー
クラスター + ロード バランシング 動的リソースと
静的リソースの分離、電流制限 (トークン バケット、リーキー バケット アルゴリズムに基づく)
アプリケーション レベルのキャッシュ、インターフェイス アンチブラシ電流制限、キュー、Tomcat パフォーマンス最適化
非同期 メッセージ ミドルウェア
Redis ホットスポット データ オブジェクト キャッシュ
分散ロック、データベース ロック、
5 分以内の支払いなし、注文のキャンセル、在庫の復元など。

4.1.2.2 メモリ リークはメモリ オーバーフローを引き起こす

ThreadLocal によって引き起こされるメモリ リークは、最終的にメモリ オーバーフローを引き起こします。

public class TLController {
    
    
 @RequestMapping(value = "/tl")
 public String tl(HttpServletRequest request) {
    
    
     ThreadLocal<Byte[]> tl = new ThreadLocal<Byte[]>();
     // 1MB
     tl.set(new Byte[1024*1024]);
     return "ok";
 }
}

(1) Alibaba Cloud サーバー
jvm-case-0.0.1-SNAPSHOT.jarにアップロード
(2) 起動

java -jar -Xms1000M -Xmx1000M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof  jvm-case-0.0.1-SNAPSHOT.jar

(3) jmeter を使用して 10000 の同時実行をシミュレートする

39.100.39.63:8080/tl

(4) トップコマンドビュー

top
top -Hp PID

(5) jstack はスレッドの状況をチェックし、デッドロックや IO ブロックがないことを確認します。

jstack PID
java -jar arthas.jar   --->   thread

(6) ヒープメモリの使用量を確認し、ヒープメモリの使用率が88.95%に達していることを確認します。

jmap -heap PID
java -jar arthas.jar   --->   dashboard

(7) この時点でメモリリークが発生しメモリオーバーフローが発生していると大まかに判断できるのですが、どのように対処すればよいのでしょうか?

jmap -histo:live PID | more
获取到jvm.hprof文件,上传到指定的工具分析,比如heaphero.io

4.2 GC

ここでは、例として G1 ガベージ コレクターのチューニングを取り上げます。

4.2.1 G1を選択するかどうか

公式ウェブサイト: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases

(1) ヒープの 50% 以上が生き残ったオブジェクトによって占有されている
(2) オブジェクトの割り当てと昇格の速度は大きく異なる
(3) ガベージ コレクション時間が比較的長い

4.2.2 G1 チューニング

(1) G1GC ガベージ コレクターを使用します。 -XX:+UseG1GC を使用し
て構成パラメーターを変更し、GC ログを取得し、GCViewer を使用してスループットと応答時間を分析します。

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  99.16%         0.00016s         0.0137s        0.00559s          12

(2) メモリサイズを調整してgcログ解析を取得する

-XX:MetaspaceSize=100M
-Xms300M
-Xmx300M

たとえば、ヒープ メモリのサイズを設定し、GC ログを取得し、GCViewer を使用してスループットと応答時間を分析します。

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.89%          0.00021s        0.01531s       0.00538s           12

(3) 最大一時停止時間を調整する

-XX:MaxGCPauseMillis=200    设置最大GC停顿时间指标

たとえば、最大一時停止時間を設定し、GC ログを取得し、GCViewer を使用してスループットと応答時間を分析します。

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.96%          0.00015s        0.01737s       0.00574s          12

(4) コンカレントGC開始時のヒープメモリ使用率

-XX:InitiatingHeapOccupancyPercent=45 
G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%).

たとえば、パーセントパラメータを設定し、GC ログを取得し、GCViewer を使用してスループットと応答時間を分析します。

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  98.11%          0.00406s        0.00532s       0.00469s          12

4.2.3 G1 チューニングのベスト プラクティス

公式サイト:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#r
推奨事項

(1) 新世代と旧世代のサイズを手動で設定しないでください。 、ヒープ全体を設定するだけです サイズの
理由: https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc

G1 コレクターの動作中に、新しい世代と古い世代のサイズを独自に調整します。
実際には、一時停止を実現するために、アダプト世代のサイズを通じてオブジェクトのプロモーションの速度と経過時間を調整します。コレクタに設定された目標時間
サイズは手動で設定すれば問題ありません G1の自動チューニングを放棄することを意味します

(2) 一時停止時間目標を継続的に調整する

通常の状況では、この値を 100 ミリ秒または 200 ミリ秒に設定できます (状況によって異なります) が、50 ミリ秒に設定するのは合理的ではありません。一時停止時間を短く設定しすぎると、G1 がガベージの生成速度に追いつけなくなります。最終的には Full GC に縮退します。したがって、このパラメータの調整は継続的なプロセスであり、徐々に最適な状態に調整されます。一時停止時間は単なる目標であり、常に達成できるわけではありません。

(3) -XX:ConcGCThreads=n を使用して、マークされたスレッドの数を増やします。

IHOP しきい値の設定が高すぎると、オブジェクトの転送時にスペースが不足するなど、転送が失敗する危険性があります。しきい値の設定が低すぎると、マーキング サイクルが頻繁に実行されすぎ、混合コレクションによって領域が再利用されなくなる可能性があります。
IHOP 値が適切に設定されているにもかかわらず、同時サイクル時間が長すぎる場合は、同時スレッドの数を増やし、ConcGCThreads を増やすことを試みることができます。

(4) MixedGC チューニング

-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
-XX:G1MixedGCCountTarger
-XX:G1OldCSetRegionThresholdPercent

(5) ヒープメモリサイズを適切に増加する
(6) 異常な Full GC

システムの起動直後にフル GC が発生することがありますが、古い世代のスペースは比較的十分にあります。これは通常、メタスペース領域が原因です。その他の値は、256M など、MetaspaceSize を通じて適切に増やすことができます。

4.3 JVM パフォーマンス最適化ガイド

ここに画像の説明を挿入

4.4 ガベージコレクションの事前チューニング

1. オンラインにする前にメモリ構成が同時実行要件を満たしているかどうか、要件を満たしていない場合は調整する必要があります。

オブジェクトのメモリレイアウトから計算し、ヒープメモリのサイズを計算し、*同時実行時間でヒープメモリの使用量を計算し、ロードバランシングと冗長性を考慮してメモリが保持できるかどうかを計算します。

2. 圧力試験のスループットはどのくらいですか (通常は 95% 以上に制御されます)

極端なスループットの場合、偏差は 1% を超えません。一時停止時間を調整し、GC ビューまたはその他の GC ツールを使用して分析します。

Throughput       Min Pause       Max Pause      Avg Pause       GC count
  99.16%         0.00016s         0.0137s        0.00559s          12

3. Full GC と Young GC の頻度、GC コレクターの交換要否

たとえば、前述の G1

4. 信頼性の観察: メモリリークがあるかどうか、これはメモリの変更に従って観察できます。

2 つの GC を実行し、ダンプされたファイルのメモリ移動とファイルのリサイクルを分析できます。

5. ガベージコレクタのパラメータを変更する必要があるかどうかも、スループットと GC に応じて決定されます

これは 2 番目のケースと組み合わせて使用​​されます

6. CPU使用率

デッドロックとメモリリークを分析する

デッドロック: jstack

4.5 よくある質問

(1) メモリリークとメモリオーバーフローの違い

メモリ リークとは、使用されなくなったオブジェクトが適時にリサイクルできず、メモリ領域を占有し続け、メモリ領域の無駄が生じることを意味します。
メモリ リークはメモリ オーバーフローを引き起こしやすいですが、メモリ オーバーフローは必ずしもメモリ リークによって引き起こされるわけではありません。

(2)若いgcにはstwがあるのでしょうか?

どのような GC、stop-the-world が送信されるかに関係なく、違いはそれが発生する時間の長さです。そして、この時間はガベージ コレクターと関係があります。シリアル、PartNew、およびパラレル スカベンジ コレクターは、シリアルかパラレルかに関係なくユーザー スレッドを一時停止しますが、CMS と G1 は、同時にマークするときにユーザー スレッドを一時停止しません
。ユーザー スレッドは一時停止され、ワー​​ルドが停止するまでの時間は
比較的短くなります。

(3) メジャーGCとフルGCの違い

メジャー GC は多くの参考資料でフル GC に相当し、多くのパフォーマンス監視ツールではマイナー GC とフル GC しか存在しないこともわかります。通常の状況では、フル GC は若い世代、古い世代、メタスペース、およびオフヒープ メモリに対してガベージ コレクションを実行します。フル GC をトリガーする理由は数多くあります。若い世代が古い世代のオブジェクト サイズに昇格され、古い世代の残りの領域より大きい場合、古い世代の領域使用量が超過した場合、フル GC がトリガーされます。一定の閾値を超えるとFull GCが発生し、メタスペースが不足している場合(JDK1.7の永続生成が不足している場合)もFull GCが発生し、System.gc()が呼び出された場合もFull GCが発生します。

(4) 直接記憶とは

Java の NIO ライブラリを使用すると、Java プログラムでダイレクト メモリを使用できるようになります。ダイレクト メモリは、Java ヒープの外側のシステムに直接適用されるメモリ空間です。通常、ダイレクト メモリへのアクセスは Java ヒープよりも高速です。したがって、パフォーマンスを考慮して、読み取りと書き込みが頻繁に行われる場合にはダイレクト メモリを検討することもできます。ダイレクト メモリは Java ヒープの外側にあるため、そのサイズは Xmx で指定された最大ヒープ サイズによって直接制限されませんが、システム メモリは制限されており、Java ヒープとダイレクト メモリの合計は依然として最大値によって制限されます。オペレーティング システムが提供できるメモリ。

(5) ゴミの判断方法

参照カウント方法: オブジェクトがどこかで参照されていれば+1、無効であれば-1、0になったらリサイクルするという意味ですが、JVMではこの方法を使用
しません相互の循環参照を決定できないためです (A は B を参照し、B は A のケースを参照します)。
参照チェーンメソッド:GC ROOTオブジェクト(メソッド領域などの静的変数で参照されるオブジェクト - 静的変数)で判断し、GC ROOTに到達できるチェーンがあれば
再利用可能であることを意味します。

(6) 到達不可能なオブジェクトはリサイクルする必要がありますか?

到達可能性分析メソッドで到達不能なオブジェクトであっても「死ななければならない」わけではありません。プロパティ分析で到達不能なオブジェクトは、初めてマークされて一度スクリーニングされ、そのオブジェクトに対してファイナライズメソッドを実行する必要があるかどうかがスクリーニング条件となります。 。オブジェクトがファイナライズ メソッドをカバーしていない場合、またはファイナライズ メソッドが仮想マシンによって呼び出されている場合、仮想マシンはこれら 2 つのケースを実行する必要がないとみなします。
実行する必要があると判断されたオブジェクトは 2 番目のマークのキューに入れられ、そのオブジェクトが参照チェーン上のオブジェクトに関連付けられていない限り、実際にはリサイクルされます。

(7) 新しい世代と古い世代を区別する必要があるのはなぜですか?

現在の仮想マシンのガベージコレクションは、オブジェクトのライフサイクルに応じてメモリをいくつかのブロックに分割する世代別コレクションアルゴリズムを採用しています。一般に、Java ヒープは新世代と旧世代に分かれており、それぞれの時代の特性に応じて適切なガベージ コレクション アルゴリズムを選択できます。
たとえば、新しい世代では、収集されるたびに多数のオブジェクトが消滅するため、コピー アルゴリズムを選択でき、各ガベージ コレクションを完了するために少額のオブジェクト コピー コストを支払うだけで済みます。オブジェクトが古い時代に生き残る確率は比較的高く、その割り当てを保証するための追加スペースがないため、ガベージ コレクションには「mark-clear」または「mark-compact」アルゴリズムを選択する必要があります。

(8) G1とCMSの違いは何ですか

CMS は主に古い世代のコレクションに焦点を当てますが、G1 は若い世代の Young GC と古い世代の Mix GC を含む世代のコレクションに焦点を当てます。

G1 はリージョンメソッドを使用してヒープメモリを分割し、全体としてガベージフラグメントの生成を減らすマークソートアルゴリズムに基づいて実装しており、初期化マークフェーズでは、到達可能なオブジェクトを検索するために使用されるカードテーブルを実装しています別の方法で。

(9) メソッド領域の不要なクラスの再利用

メソッド領域は主に無駄なクラスを再利用するのですが、無駄なクラスかどうかはどうやって判断するのでしょうか?
定数が「放棄定数」であるかどうかを判断することは比較的簡単ですが、クラスが「役に立たないクラス」であるかどうかを判断するための条件は比較的厳しいです。
クラスが「役に立たないクラス」とみなされるには、次の 3 つの条件を同時に満たす必要があります。
a - このクラスのすべてのインスタンスがリサイクルされている、つまり、Java ヒープにこのクラスのインスタンスが存在しない。
b - このクラスをロードした ClassLoader はリサイクルされました。
c- このクラスに対応する java.lang.Class オブジェクトはどこからも参照されておらず、このクラスのメソッドにはどこからもリフレクションを通じてアクセスできません。

おすすめ

転載: blog.csdn.net/lx9876lx/article/details/129150878