多くのエンタープライズアプリケーション、特にOLTPアプリケーションでは、長い一時停止によってサービスタイムアウトが発生する可能性があります。JVMで実行されているこれらのアプリケーションでは、ガベージコレクション(GC)が長い一時停止の主な理由である可能性があります。この記事では、GCの一時停止が発生する可能性のあるいくつかの異なるシナリオについて説明し、これらのGCの一時停止をトラブルシューティングして解決する方法について説明します。
以下は、アプリケーションの実行中に長いGC一時停止を引き起こす可能性のあるいくつかの異なるシナリオです。
1.断片化
これは間違いなく最初にランク付けする必要があります。CMSの最も致命的な欠陥である断片化の問題が原因で、OLAPシステムを10年以上支配してきたガベージコレクターが歴史の段階から直接撤退したためです(CMSはすでに非推奨であり、将来のバージョンは削除されます。これらの構成を大切にしてくださいCMSのJVM)は、G1と最新のZGCに直面して、本質的に無効化された(断片化された)CMSの欠如(スライス)に反撃するものは何もありません。
CMSの場合、旧世代の断片化の問題により、YGCでプロモーションの失敗が発生する可能性があります(プロモーションの失敗、旧世代に十分な有効スペースがある場合でも、十分な連続スペースがないため、割り当ての失敗が発生する可能性があります)。コンカレントモード障害をトリガーすると、完全にSTWになるFullGCが発生します。FullGCは、CMSの同時モードよりも、ガベージコレクション作業を完了するために長い休止時間を必要とします。これは、Javaアプリケーションにとって間違いなく最大の災害の1つです。
CMSシナリオに断片化があるのはなぜですか?CMSは、古い時代にリサイクルするときにMark-Sweepアルゴリズムを使用するため、ガベージコレクション中にヒープを圧縮しません。時間の経過とともに、古い時代の断片化の問題は、シングルスレッドになるまでますます深刻になります。 Mark-Sweep-Compact GC、FullGCは完全にSTWになります。ヒープが比較的大きい場合、STW時間は数秒、さらには10秒、または数十秒かかる場合があります。
次のcmsgcログでは、フラグメンテーション率が非常に高いため、プロモーションの失敗と同時モードの失敗が発生しました。トリガーされたFullGCは、完了するまでに合計17.1365396秒かかりました。
{Heap before GC invocations=7430 (full 24):
parnew generation total 134400K, used 121348K[0x53000000, 0x5c600000, 0x5c600000)
eden space 115200K, 99% used [0x53000000, 0x5a07e738, 0x5a080000)
from space 19200K, 32% used [0x5a080000, 0x5a682cc0, 0x5b340000)
to space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)
concurrent mark-sweep generation total 2099200K, used 1694466K [0x5c600000, 0xdc800000, 0xdc800000)
concurrent-mark-sweep perm gen total 409600K, used 186942K [0xdc800000, 0xf5800000, 0xfbc00000)
10628.167: [GC Before GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 103224160
Max Chunk Size: 5486
Number of Blocks: 57345
Av. Block Size: 1800
Tree Height: 36 <---- High fragmentation
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 371324
Max Chunk Size: 254
Number of Blocks: 8591 <---- High fragmentation
Av. Block Size: 43
free=103595484
frag=1.0000 <---- High fragmentation
Before GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
Tree Height: 0
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
free=0 frag=0.0000
10628.168: [ParNew (promotion failed) Desired survivor size 9830400 bytes, new threshold 1 (max 1)
- age 1: 4770440 bytes, 4770440 total: 121348K->122157K(134400K), 0.4263254secs]
10628,594: [CMS10630.887: [CMS-concurrent-mark: 7.286/8.682 secs] [Times: user=14.81, sys=0.34, real=8.68 secs]
(concurrent mode failure):1698044K->625427K(2099200K), 17.1365396 secs]
1815815K->625427K(2233600K), [CMS Perm : 186942K->180711K(409600K)]
After GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 377269492
Max Chunk Size:
377269492
Number of Blocks: 1 <---- No fragmentation
Av. Block Size: 377269492
Tree Height: 1 <---- No fragmentation
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
free=377269492
frag=0.0000 <---- No fragmentation
After GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
Tree Height: 0
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
free=0 frag=0.0000
, 17.5645589 secs] [Times: user=17.82 sys=0.06, real=17.57 secs]
Heap after GC invocations=7431 (full 25):
parnew generation total 134400K, used 0K [0x53000000, 0x5c600000, 0x5c600000)
eden space 115200K, 0% used [0x53000000, 0x53000000, 0x5a080000)
from space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)
to space 19200K, 0% used [0x5a080000, 0x5a080000, 0x5b340000)
concurrent mark-sweep generation total 2099200K, used 625427K [0x5c600000, 0xdc800000, 0xdc800000)
concurrent-mark-sweep perm gen total 409600K, used 180711K [0xdc800000, 0xf5800000, 0xfbc00000)
}
Total time for which application threads were stopped: 17.5730653 seconds
2.GC中のオペレーティングシステムのアクティビティ
GCが発生すると、スワップなどの一部のオペレーティングシステムアクティビティにより、GCの一時停止時間が長くなる場合があります。これらの一時停止は、数秒または数十秒になる場合があります。
システムがスワップスペースの使用を許可するように構成されている場合、オペレーティングシステムはJVMプロセスの非アクティブなメモリページをスワップスペースに移動し、それによって現在アクティブなプロセス(システムのスケジュールによってはオペレーティングシステム上の他のプロセス)にメモリを解放する場合があります。スワッピングはディスクにアクセスする必要があるため、物理メモリと比較して、その速度はひどく遅いです。したがって、システムがGC中にスワッピングを実行する必要があるだけの場合、GCの一時停止時間は非常に非常に恐ろしいものになります。
以下は、29.48秒続いたYGCログです。
{Heap before GC invocations=132 (full 0):
par new generation total 2696384K, used 2696384K [0xfffffffc20010000, 0xfffffffce0010000, 0xfffffffce0010000)
eden space 2247040K, 100% used [0xfffffffc20010000, 0xfffffffca9270000, 0xfffffffca9270000)
from space 449344K, 100% used [0xfffffffca9270000, 0xfffffffcc4940000, 0xfffffffcc4940000)
to space 449344K, 0% used [0xfffffffcc4940000, 0xfffffffcc4940000, 0xfffffffce0010000)
concurrent mark-sweep generation total 9437184K, used 1860619K [0xfffffffce0010000, 0xffffffff20010000, 0xffffffff20010000)
concurrent-mark-sweep perm gen total 1310720K, used 511451K [0xffffffff20010000, 0xffffffff70010000, 0xffffffff70010000)
2013-07-17T03:58:06.601-0700: 51522.120: [GC Before GC: :2696384K->449344K(2696384K), 29.4779282 secs] 4557003K->2326821K(12133568K) ,29.4795222 secs] [Times: user=915.56, sys=6.35, real=29.48 secs]
最後の行[時間:user = 915.56、sys = 6.35、real = 29.48秒]で、realはYGCが適用されたときの実際の一時停止時間です。
YGCが発生したこの時点で、vmstatコマンドの出力は次のようになります。
r b w swap free re mf pi po fr de sr s0 s1 s2 s3 in sy cs us sy id
0 0 0 77611960 94847600 55 266 0 0 0 0 0 0 0 0 0 3041 2644 2431 44 8 48
0 0 0 76968296 94828816 79 324 0 18 18 0 0 0 0 1 0 3009 3642 2519 59 13 28
1 0 0 77316456 94816000 389 2848 0 7 7 0 0 0 0 2 0 40062 78231 61451 42 6 53
2 0 0 77577552 94798520 115 591 0 13 13 0 0 13 12 1 0 4991 8104 5413 2 0 98
YGCの完了には合計29秒かかりました。vmstatコマンドの出力は、この期間中に使用可能なスワップスペースが600m減少したことを示しています。これは、GC中に、メモリ内の一部のページがスワップスペースに移動されることを意味します。このメモリページは、必ずしもJVMプロセスに属しているとは限りませんが、他のオペレーティングシステム上の他のプロセスである可能性があります。
上記のことから、オペレーティングシステムで利用可能な物理コンテンツでは、システム上のすべてのプロセスを実行するのに十分ではないことがわかります。解決策は、実行するプロセスをできるだけ少なくし、RAMを増やしてシステムの物理メモリを増やすことです。この例では、旧エリアには9Gがありますが、1.8G(マークスイープ生成の合計9437184K、1860619Kを使用)のみが使用されています。Old領域のサイズとヒープ全体のサイズを適切に縮小できるため、メモリの負荷が軽減され、システム上のアプリケーションでスワップが発生する可能性が最小限に抑えられます。
スワッピングに加えて、長いGC一時停止中のIOまたはネットワークアクティビティを監視および理解する必要があります。これは、iostatとnetstatの2つのツールを使用して実行できます。また、mpstatを使用してCPU統計を表示し、GCのタイミングを把握することもできます。十分なCPUリソースがあるかどうか。
3.十分なヒープスペースがありません
アプリケーションに必要なメモリが実行中のXmxよりも大きい場合、ガベージコレクションが頻繁に発生し、さらにOOMが発生します。不十分なヒープスペースとオブジェクト割り当ての失敗により、JVMは割り当てられたスペースを再利用するためにGCを呼び出す必要がありますが、GCはそれ以上のスペースを解放できず、GCに再びつながり、悪循環に入ります。
アプリケーションの実行中、FullGCを頻繁に実行すると、長い一時停止が発生します。次の例では、Permスペースがほぼいっぱいになり、Perm領域にメモリを割り当てようとしても失敗し、FullGCがトリガーされます。
166687.013: [Full GC [PSYoungGen:126501K->0K(922048K)] [PSOldGen: 2063794K->1598637K(2097152K)]2190295K->1598637K(3019200K) [PSPermGen: 165840K->164249K(166016K)],6.8204928 secs] [Times: user=6.80 sys=0.02, real=6.81 secs]
166699.015: [Full GC [PSYoungGen:125518K->0K(922048K)] [PSOldGen: 1763798K->1583621K(2097152K)]1889316K->1583621K(3019200K) [PSPermGen: 165868K->164849K(166016K)],4.8204928 secs] [Times: user=4.80 sys=0.02, real=4.81 secs]
同様に、古い時代のスペースが十分でない場合は、FullGCが頻繁に発生します。この種の問題は処理が簡単です。古い世代や永続的な世代にとっては、けちすぎないでください。
4.JVMのバグ
すべてのソフトウェアにはバグがあり、JVMも例外ではありません。GCの長い一時停止は、BUGが原因である場合があります。たとえば、以下にリストされているこれらのJVMバグにより、JavaアプリケーションがGC中に長時間一時停止する可能性があります。
6459113: CMS+ParNew: wildly different ParNew pause times depending on heap shape caused by allocation spread
fixed in JDK 6u1 and 7
6572569: CMS: consistently skewed work distribution indicated in (long) re-mark pauses
fixed in JDK 6u4 and 7
6631166: CMS: better heuristics when combatting fragmentation
fixed in JDK 6u21 and 7
6999988: CMS: Increased fragmentation leading to promotion failure after CR#6631166 got implemented
fixed in JDK 6u25 and 7
6683623: G1: use logarithmic BOT code such as used by other collectors
fixed in JDK 6u14 and 7
6976350: G1: deal with fragmentation while copying objects during GC
fixed in JDK 8
JDKが上記のバージョンである場合は、バグが修正されたバージョンにアップグレードすることを強くお勧めします。
5.System.gc呼び出しを表示します
System.gc呼び出しが表示されているかどうかを確認します。アプリケーションまたはサードパーティモジュールの一部のクラスでは、System.gcを呼び出してSTWのFullGCをトリガーすると、非常に長い一時停止が発生する場合があります。次のGCログに示されているように、Full GCの背後にある(System)は、System.GCを呼び出すことによってFullGCがトリガーされ、5.75秒かかることを示しています。
164638.058: [Full GC (System) [PSYoungGen: 22789K->0K(992448K)]
[PSOldGen: 1645508K->1666990K(2097152K)] 1668298K->1666990K(3089600K)
[PSPermGen: 164914K->164914K(166720K)], 5.7499132 secs] [Times: user=5.69, sys=0.06, real=5.75 secs]
RMIを使用する場合、RMIの実装はSystem.gcを呼び出すため、一定の時間間隔でFullGCを監視できます。この時間間隔は、システムプロパティを介して構成できます。
-Dsun.rmi.dgc.server.gcInterval=7200000
-Dsun.rmi.dgc.client.gcInterval=7200000
JDK 1.4.2および5.0のデフォルト値は60000ミリ秒(1分)です。JDK6以降のバージョンの場合、デフォルト値は3600000ミリ秒(1時間)です。
System.gc()を呼び出してトリガーFullGCをオフにする場合は、JVM構成パラメーター-XX:+DisableExplicitGC
をオフにすることができます。
では、そのような問題を見つけて解決する方法は?
-
JVMパラメーターを構成します。-XX:+ PrintGCDetails -XX:+ PrintHeapAtGC -XX:+ PrintGCTimeStamps -XX:+ PrintGCDateStampsおよび-XX:+PrintGCApplicationStoppedTime。CMSの場合は、-XX:PrintFLSStatistics = 2も追加して、GCログを収集する必要があります。GCログはGCの頻度、長時間一時停止しているかどうか、その他の重要な情報を教えてくれるからです。
-
vmstat、iostat、netstat、mpstatなどのツールを使用して、システムの全体的な状態を監視します。
-
GCHistoツールを使用してGCログを視覚的に分析し、長時間消費したGCと、これらのGCの外観に特定のパターンがあるかどうかを確認します。
-
GCログからJVMヒープの断片化の兆候を見つけてください。
-
指定されたアプリケーションのヒープサイズが十分であるかどうかを監視します。
-
実行しているJVMのバージョンを確認し、長い一時停止に関連するBUGがあるかどうかを確認してから、問題を修正する最新のJDKにアップグレードします。