ホームページ  >  記事  >  Java  >  ミッションクリティカルなJavaアプリケーションのガベージコレクション最適化について詳しく解説(後編)

ミッションクリティカルなJavaアプリケーションのガベージコレクション最適化について詳しく解説(後編)

黄舟
黄舟オリジナル
2017-03-23 11:03:441108ブラウズ

ミッションクリティカルな Java アプリケーション向けのガベージ コレクションの最適化 (パート 1)

パラレル マーク スイープ (CMS) コレクター

CMS ガベージ コレクターは、広く使用されている最初の低遅延コレクターでした。 Java 1.4.2 で利用可能ですが、最初はあまり安定していません。これらの問題は Java 5 まで解決されませんでした。

CMS コレクターの名前から、並列方式を使用していることがわかります。リサイクル作業のほとんどは、ユーザーのリクエストを処理するワーカー スレッドと並行して実行される GC スレッドによって完了します。旧世代の元の 1 つの世界トップリサイクル プロセスは、2 つの短いストップ ザ ワールド一時停止と 5 つの並行ステージに分割されています。これらの並列フェーズ中、元のワーカー スレッドは通常どおり (一時停止せずに) 実行されます。

CMS リサイクラーは、次のパラメータを使用してアクティブ化できます:

-XX:+UseConcMarkSweepGC

上記のテスト プログラムを再度適用すると (負荷を増やして)、次の結果が得られます:

図 4 ヒープ サイズが最適化された JVM と 50 で CMS を使用する(-Xms1200m -Xmx1200m -XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6 -XX:+UseConcMarkSoupGC)) 内の GC の動作

旧世代の GC の 8 秒の一時停止が消えていることがわかります。 。現在、古い世代の収集プロセスでは一時停止が 2 回のみになり (前のプロセスでは 50 時間で 5 回の一時停止が発生しました)、すべての一時停止は 1 秒以内です。

デフォルトでは、CMS コレクターは ParNew (GC アルゴリズム) を使用して新しい世代のコレクションを処理します。 ParNew を CMS とともに実行すると、CMS 間で追加の調整が必要となるため、CMS を使用しない場合よりも一時停止時間が少し長くなります。前回のテスト結果と比較して、この問題は、新しい世代の平均休止時間がわずかに増加していることからわかります。新しい世代の一時停止時間では外れ値が頻繁に発生しますが、この問題はここでも見られます。外れ値は約 0.5 秒に達する可能性があります。ただし、これらの一時停止は多くのアプリケーションにとって十分短いため、CMS と ParNew の組み合わせは、優れた低遅延最適化オプションとして機能します。

CMS コレクターの重大な欠陥は、古い世代のスペースがいっぱいになると CMS を起動できないことです。古い世代がいっぱいになると、CMS を起動するには遅すぎます。VM は通常の「stop-the-world」戦略を使用する必要があります (「同時モード障害」レコードが

GC ログ に表示されます)。低レイテンシーの目標を達成するには、古い世代のスペース使用量が特定のしきい値に達したときに CMS コレクターを開始する必要があります。これは次の設定によって達成されます:

-XX:CMSInitiatingOccupancyFraction=80

これは、古い世代のスペースが 80% 占有されると、という意味です。 , CMS コレクターが実行されます。このアプリケーションでは、この値 (デフォルト値) をそのまま使用します。ただし、しきい値の設定が高すぎると、「同時モード障害」が発生し、古い世代の GC が長期間停止することになります。一方、設定が低すぎる (アクティブ領域サイズ未満) と、CMS が常に並列実行され、特定の CPU コアが GC に完全に使用されてしまう可能性があります。対話型メソッドやタイマーを介して特殊なタスクを起動するなど、アプリケーションのオブジェクト作成やヒープ使用の動作が急速に変化する場合、上記 2 つの問題を回避しながら適切なしきい値を設定することは困難です。

フラグメントの影

ただし、CMS の最大の問題の 1 つは、旧世代のヒープ領域をデフラグできないことです。これによりヒープの断片化が生じ、時間が経つと深刻なサービス低下を引き起こす可能性があります。これは、古い世代のスペースが狭いことと、CMS が頻繁にリサイクルされることの 2 つの要因が原因である可能性があります。最初の要因は、ParallelGC コレクターが必要とする領域よりも大きい、旧世代のヒープ領域を増やすことで改善できます (最初の数枚の写真からわかるように、私はそれを 1024M から 1200M に増やしました)。 2 番目の問題は、前述したように、各世代の空間を適切に分割することで最適化できます。これにより、旧世代の GC の頻度がどれだけ削減されるかが実際にわかります。

CMS を使用する前に各世代のヒープ サイズを合理的に調整することが重要であることを証明するために、まず、上記の原則に従わず、図に基づいて CMS コレクターを直接使用した場合に何が起こるかを見てみましょう。 1 (ヒープ最適化はほとんどなし):

図 5 ヒープサイズを最適化していない場合の GC の動作と、CMS 使用後のメモリ断片化によるパフォーマンス低下 (14 時間目以降)

JVM が安定して動作していることがわかりますこのような設定での負荷テストは 14 時間近く行われます (この無害な不安定段階は、運用環境や負荷が小さい場合にはさらに長く続く可能性があります)。次に、突然複数の長い GC 一時停止が発生し、残り時間のほぼ半分を占めます。古い世代の一時停止時間は 10 秒以上に達するだけでなく、新しい世代の一時停止時間も数秒に達します。これは、コレクターがオブジェクトを新世代から旧世代に移動するために、旧世代のスペースを検索するのに長い時間を費やす必要があるためです。

CMS低延迟优点的代价就是内存碎片。这个问题可以最小化,但是不会彻底消失。你永远不知道它什么时候会被触发。然而,通过合理的优化与监控可以控制它的风险。

G1(Garbage First)回收器的希望

G1回收器设计的目的就是保证低延迟的同时而没有堆碎片风险。因此,Oracle把它作为CMS的一个长期取代。G1可以避免碎片风险是因为它会整理堆空间。对于GC暂停来说,G1的目标并不是使暂停时间最小化,而是设置一个时间上限,使GC暂停尽量满足这一上限值。

在将G1回收器用于测试程序中并与上述其他经典回收器做对比之前,先总结两点关于G1的重要信息。

  • Oracle在Java 7u4中开始支持G1。为了使用G1你应该将Java 7更新到最新。Oracle的GC团队一直致力于G1的研发,在最新的Java更新中(本文编写时最新版本是7u7到7u9),G1的改进很显著。另一方面,G1无法在任何Java 6版本中使用,而且到目前更优越的Java 7不可能向后移植到Java 6中。

  • 前面关于调节各代空间大小的优化对G1来说已经淘汰了。设置各代空间大小与设置暂停目标时间相冲突会使G1回收器偏离原本的设计目标。使用G1时,可以使用“-Xms”和“-Xmx”设置整体的内存大小,也可以设置GC暂停目标时间(可选),对G1来说不用设置其他选项。与ParallelGC回收器的AdapativeSizingPolicy类似,它自适应地调整各代空间大小来满足暂停目标时间。

遵循这些原则后,G1回收器在默认配置下的结果如下:

图6 最小配置(-Xms1024m -Xmx1024 -XX:+UseG1GC)的JVM在G1下26小时内的GC性能

在这个例子中,我们使用了默认的GC暂停目标时间200ms。从图中可以看到,平均时间与这个目标比较吻合,最长GC暂停时间与使用CMS回收器差不多(图4)。G1明显可以很好地控制GC暂停,与平均时长相比,离群值也相当少。

另一方面,平均GC暂停时间要比CMS回收器长很多(270 vs 100ms),而且更频繁。这意味着GC累积暂停时间(也就是GC本身所占总时间)是使用CMS的4倍以上(6.96% vs 1.66%)。

与CMS一样,G1也分为GC暂停阶段和并行回收阶段(不暂停任务)。同样与CMS类似,当堆占用比达到一定门限后,它才启动并行回收阶段。从图6可以看到,1GB的可用内存到目前为止并没有完全使用。这是因为G1的默认占用比门限值要比CMS低很多。也有人指出,一般来说较小的堆空间就可以满足G1的需求。

垃圾回收器的定量比较

下面的表格总结了Oracle Java 7中4种最重要的垃圾回收器在测试中的关键性能指标。在同样的应用程序上,进行相同的负载测试,但是负载的级别不同(由第2列的垃圾创建速率体现)。

表 几种垃圾回收器的比较

所有的回收器都运行在1GB的堆空间上。传统的回收器(ParallelGC、ParNewGC和CMS)另外使用下面的堆设置:

-XX:NewSize=400m -XX:MaxNewSize=400m -XX:SurvivorRatio=6

而G1回收器没有额外的堆大小设置,并且使用默认的暂停目标时间200ms,也可以显示设置:

-XX:MaxGCPauseMillis=200

从表中可以看到,传统回收器在新生代回收上(第3列)时间差不多。对ParallelGC和ParNewGC来说是差不多的,而CMS实际上也是使用ParNewGC去回收新生代。然而,在新生代GC暂停中,将新生代存活对象移入老年代需要ParNewGC和CMS的协同。这样的协同引入额外的代价,也就导致CMS的新生代GC暂停时间要略长。

第7列是GC暂停所耗费的时间占总时间的百分比,这个值可以很好地反映GC的总时间代价。因为并行GC总时间(最后一列)以及引入的CPU占用代价可以忽略。按前文所述,优化堆大小后老年代GC次数会变得很少,这样第7列的值主要由新生代GC暂停总时间所决定。新生代暂停总时间是新生代暂停(连续)时长(第3列)与暂停次数的乘积。新生代暂停频率与新生代空间大小有关,对传统回收器来说,这个大小是相同的(400MB)。因此,对传统回收器来说,第7列的值或多或少地反映着第3列的值(负载差不多的情况)。

CMS の利点は列 6 から明確にわかります。CMS は、総時間コストがわずかに長くなり、旧世代の GC 一時停止が短くなります (1 桁低くなります)。現実世界の多くのアプリケーションでは、これは適切な妥協策です。

では、G1 コレクターはアプリケーションに対してどのように動作するのでしょうか?列 6 (および列 5) でわかるように、G1 コレクターは、旧世代の GC 一時停止時間を短縮する点で CMS コレクターよりも優れた仕事をしています。しかし、列 7 からもわかるように、非常に高い代償を払っています。同じ負荷の下では、GC の合計時間コストは 7% を占めるのに対し、CMS は 1.6% しか占めていません。

今後の記事では、G1 でより高い GC 時間コストが発生する原因を検証し、他のコレクター (特に CMS コレクター) と比較した G1 の長所と短所も分析します。これは大きくて貴重なトピックです。

概要と展望

すべての古典的な Java GC アルゴリズム (SerialGC、ParallelGC、ParNewGC、CMS) では、各世代のヒープ領域サイズを最適化することが非常に重要ですが、実際には、多くのアプリケーションは十分な合理的な最適化を行っていません。 。その結果、アプリケーションのパフォーマンスが十分に最適化されず、動作が低下します (十分に監視しないと、パフォーマンスの低下やプログラムの一時停止を引き起こすこともあります)。

各世代のヒープ領域サイズを最適化すると、アプリケーションのパフォーマンスが大幅に向上し、GC の長い一時停止の回数を最小限に抑えることができます。次に、長い GC 一時停止をなくすには、低遅延コレクターを使用する必要があります。 CMS は (これまで) 効率的で低遅延のコレクターとして好まれてきました。多くの場合、CMS で十分です。適切な最適化を行えば、長期的な安定性を確保できますが、ヒープの断片化のリスクがあります。

代替手段として、G1 コレクターは現在 (Java 7u9) サポートされており利用可能なオプションですが、まだ改善の余地があります。その結果は多くのアプリケーションで許容できますが、CMS コレクターと比べると十分ではありません。その利点と欠点の詳細については、慎重に検討する価値があります

以上がミッションクリティカルなJavaアプリケーションのガベージコレクション最適化について詳しく解説(後編)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。