1. JVMパフォーマンスの最適化とは何ですか
JVM パフォーマンスの最適化には、スループットと応答時間という 2 つの非常に重要な概念が含まれます。JVMチューニングは主に、理想的な目標を達成するためにそれらを調整し最適化し、ビジネスに応じてスループットを優先するか応答時間を優先するかを決定することです。
- スループット: ユーザー コードの実行時間 / (ユーザー コードの実行時間 + GC 実行時間)。
- 応答時間: インターフェイス全体の応答時間 (ユーザー コード実行時間 + GC 実行時間)。STW 時間が短いほど、応答時間も短くなります。
チューニング方法
-
JVMパフォーマンスを監視する
- JVM の実行ステータスを監視して、アプリケーションのボトルネックとパフォーマンスのボトルネックを理解します。
- jstat、jmap、jstack などの JVM に付属のツール、または VisualVM、JProfiler などのサードパーティ ツールを使用できます。
-
ストレステストのベンチマーク
- プログラムの耐圧テストを実施し、インターフェースに応じたスループットと応答時間を取得します。
- 外部現象
- ユーザーエクスペリエンスにとって重要なのは応答速度です
- 圧力測定ツール jmeter を使用して圧力測定を実行し、関連するパフォーマンス指標を取得できます。
- 内部現象:
- JVMのパフォーマンスチューニングにおいてGC状況の分析は重要な要素であり、GCの動作仕組みやGCログの意味をマスターする必要がある
- JVM または GCEasy などのサードパーティ ツールに付属の GC ログを使用して、GC の状況を分析し、GC の頻度、時間、メモリ使用量などを把握できます。
-
JVMパラメータを調整する
- ヒープ サイズ、GC アルゴリズム、スレッド プール サイズなどのパラメータを調整することで、アプリケーションのパフォーマンスを向上させます。
- 注: IO 集中型アプリケーションや CPU 集中型アプリケーションなど、アプリケーションや環境が異なると、異なる JVM パラメータ設定が必要になる場合があります。
-
二次応力解析
- jvm パラメータを調整した後、2 回目の圧力テストを実行して、パフォーマンス指標が向上したか低下したかを確認します。
- 内部: GC ログ、スループット、GC 時間、一時停止時間の変更を表示
- 外部: インターフェイスに対応するスループットと応答時間が優れているかどうか
-
その他の最適化手法
-
コードを最適化する
- 不必要なオブジェクトの作成を回避し、同期操作を減らし、キャッシュを使用するなどしてコードを最適化します。
- 注: コードの最適化は、「まず修正してから最適化する」という原則に従う必要があり、コードの可読性と保守性を犠牲にしてはなりません。
-
同時プログラミングを使用する
- マルチスレッド、スレッド プールなどを使用して、スレッド プールのキューの長さ、存続するスレッドの数などを調整するなど、同時実行パフォーマンスを向上させます。
- 注: 同時プログラミングでは、スレッド セーフやロック競合などの問題を考慮する必要があり、正しい設計と実装が必要です。
-
キャッシュを使用する
- ローカル キャッシュ、分散キャッシュなどを使用してデータ アクセスのパフォーマンスを向上させることができます。
- 注: キャッシュでは、キャッシュの一貫性やキャッシュの無効化などの問題を考慮する必要があり、正しい設計と実装が必要です。
-
IOブロックを回避する
- 非同期 IO、NIO などを使用して、以前に学習した CompletableFuture 非同期タスクの配置などの IO パフォーマンスを向上させます。
- 注: IO プログラミングでは同時実行性や信頼性などの問題を考慮する必要があり、正しい設計と実装が必要です。
-
分散+クラスタ技術
- ロードバランシング + クラスターテクノロジーを使用して、単一ノードの処理能力を向上させます。
-
2. JVMチューニングプレッシャーテスト環境の準備
- SpringBoot によって書かれた jar プログラム、インターフェースは 100 個以内でランダムに構成されたオブジェクトのリストを返します (JDK17 を使用)
/**
* @author lixiang
* @date 2023/5/8 21:44
*/
@Slf4j
@RestController
@RequestMapping("/spring-test")
public class SpringTestController {
@RequestMapping("query")
public Map<String, Object> query() throws InterruptedException {
int num = (int) (Math.random() * 100) + 1;
//申请5MB内存
Byte[] bytes = new Byte[5 * 1024 * 1024];
List<Product> productList = new ArrayList<>();
for (int i = 0; i < num; i++) {
Product product = new Product();
product.setPrice((int) Math.random() * 100);
product.setTitle("商品编号" + i);
productList.add(product);
}
Thread.sleep(5);
Map<String, Object> map = new HashMap<>(16);
map.put("data", productList);
return map;
}
}
- Jmeter 圧力テストツールの準備、テスト計画 200 同時実行、サイクル 500 回
3. JVM パフォーマンスを最適化するためのヒープ サイズ構成
- ヒープ サイズ構成、FullGC 時間のパフォーマンスへの影響
- パフォーマンスに最適化された初期値
-Xms1g # 配置初始堆内存1G
-Xmx1g # 配置最大堆内存1G
-XX:+UseG1GC # 使用G1回收器
-XX:MaxGCPauseMillis=200 # 设置最大停顿时间200ms
-XX:G1HeapRegionSize=32M # 设置G1每个region块大小为32M
-XX:ActiveProcessorCount=8 # 设置JVM使用的CPU核数限制为8
-XX:+HeapDumpOnOutOfMemoryError # 当JVM发生OOM时,自动生成DUMP文件
-XX:HeapDumpPath=heapdump.hprof # DUMP文件路径
-XX:+PrintCommandLineFlags # 监控开启
-Xlog:gc=info:file=portal_gc.log:utctime,level,tags:filecount=50,filesize=100M
# Xlog:指定日志输出方式为日志文件。
# gc*:指定日志输出类型为GC相关的日志。
# info:指定输出日志的级别为info级别。
# file=portal_gc.log:指定日志输出的文件名为portal_gc.log。
# utctime:指定日志输出的时间戳使用UTC时间。
# level,tags:指定日志输出的格式包含级别和标签信息。
# filecount=50:指定最多保存50个日志文件。
# filesize=100M:指定每个日志文件的大小为100MB。
- マシン構成は次のとおりです: 8 コア 16G 500M 帯域幅
- 初期ヒープ メモリと最大ヒープ メモリを 1G に設定し、プレッシャー テストを行います。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms1g -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
ヒープメモリを 1G に設定した場合、全体のスループットは 40% 以上と非常に低く、この期間中に Young GC が 7451 回発生し、Full GC が 142 回発生しました。
- 初期ヒープ メモリと最大ヒープ メモリを 2G に設定し、プレッシャー テストを行います。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
ヒープメモリを2Gに設定した場合、1Gに比べてスループットが70%以上に向上し、Young GC数は752回、Full GC数は6倍と1Gに比べて2倍になりました。
- 初期ヒープ メモリと最大ヒープ メモリを 4G に設定し、プレッシャー テストを行います。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
ヒープ メモリを 4G に設定すると、全体のスループットは 76% に増加し、Young GC で 504 が発生し、Full GC は発生しません。
- 初期ヒープ メモリと最大ヒープ メモリを 6G に設定し、プレッシャー テストを行います。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms6g -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &
ヒープ メモリを 6G に設定すると、全体のスループットは 87% に達し、Yong GC は 196 回発生し、Full GC は 0 回発生しました。
概要: ヒープ メモリの調整により、4G が最も入出力比の高いパラメータ構成であることが判明したため、現在の構成で 4G ヒープ メモリを使用できます。
4. JVM パフォーマンスを最適化するためのコレクター構成
上記のヒープ メモリの構成を通じて、現在のマシンとアプリケーションの構成には 4G が最適なヒープ メモリであると結論付けることができます。ここでは、ヒープ メモリのサイズは変更しませんが、ヒープ メモリの 4G を使用してガベージ メモリを変更します。コレクターを使用して、インターフェイスのスループットへの影響を確認します。
ここでは ParallelGC を使用します。現時点では、大量の同時実行を行うアプリケーションには G1 ガベージ コレクターが最適です。ここでは主に ParallelGC を比較のために使用します。
nohup java -jar spring-test-1.0-SNAPSHOT.jar -Xms4g -Xmx4g -XX:+UseParallelGC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32M -XX:ActiveProcessorCount=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/jmeter/heapdump.hprof -XX:+PrintCommandLineFlags -Xlog:gc=info:file=/usr/local/jmeter/portal_gc.log:utctime,level,tags:filecount=50,filesize=100M &