Java のスループットに対するガベージ コレクター GC の影響をテストする

高洛峰
リリース: 2017-01-17 15:48:26
オリジナル
1668 人が閲覧しました

メモリ管理用語集を見ていたら、中国語で「Pythonの豚(注:貪欲で足りない蛇が象を飲み込むような感じ)」という定義を偶然見つけたので思いつきました。記事。表面的には、この用語は、GC が大きなオブジェクトをある世代から別の世代に継続的に昇格させることを指します。これは、ニシキヘビが獲物を丸呑みして、消化している間動くことができないようにするのと同じです。

その後24時間、私の心はこの窒息するニシキヘビのイメージでいっぱいで、追い出すことができませんでした。精神科医が言うように、恐怖を和らげる最善の方法は、それを話すことです。そこでこの記事です。しかし、次に話したいのは Python ではなく、GC チューニングです。私は神に誓う。

GC の一時停止がパフォーマンスのボトルネックを引き起こしやすいことは誰もが知っています。最新の JVM には、リリース時に高度なガベージ コレクターが付属していますが、私の経験からすると、特定のアプリケーションに最適な構成を見つけるのは非常に困難です。手動チューニングにはまだ希望の光があるかもしれませんが、GC アルゴリズムの仕組みを正確に理解する必要があります。この点に関しては、この記事が役立ちます。以下では、JVM 構成の小さな変更がアプリケーションのスループットにどのような影響を与えるかを例を使って説明します。

スループットに対する GC の影響を示すために使用するアプリケーションは、単なる単純なプログラムです。これには 2 つのスレッドが含まれています:

PigEater – 巨大なニシキヘビが大きな太った豚を食べるプロセスをシミュレートします。このコードは、java.util.List に 32MB バイトを追加し、飲み込むたびに 100 ミリ秒スリープすることでこれを実現します。
PigDigester – 非同期消化のプロセスをシミュレートします。ダイジェストを実装するコードは、豚のリストを空に設定するだけです。これは面倒なプロセスなので、このスレッドはリファレンスをクリアした後、毎回 2000 ミリ秒スリープします。
両方のスレッドは while ループで実行され、ヘビがいっぱいになるまで食べて消化します。これには約5,000頭の豚を食べる必要がある。

package eu.plumbr.demo;
public class PigInThePython {
  static volatile List pigs = new ArrayList();
  static volatile int pigsEaten = 0;
  static final int ENOUGH_PIGS = 5000;
  public static void main(String[] args) throws InterruptedException {
    new PigEater().start();
    new PigDigester().start();
  }
  static class PigEater extends Thread {
    @Override
    public void run() {
      while (true) {
        pigs.add(new byte[32 * 1024 * 1024]); //32MB per pig
        if (pigsEaten > ENOUGH_PIGS) return;
        takeANap(100);
      }
    }
  }
  static class PigDigester extends Thread {
    @Override
    public void run() {
      long start = System.currentTimeMillis();
      while (true) {
        takeANap(2000);
        pigsEaten+=pigs.size();
        pigs = new ArrayList();
        if (pigsEaten > ENOUGH_PIGS)  {
          System.out.format("Digested %d pigs in %d ms.%n",pigsEaten, System.currentTimeMillis()-start);
          return;
        }
      }
    }
  }
  static void takeANap(int ms) {
    try {
      Thread.sleep(ms);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
ログイン後にコピー

ここで、このシステムのスループットを「1 秒あたりに消化できる豚の数」として定義します。 100 ミリ秒ごとに豚がこの Python に詰め込まれることを考慮すると、このシステムの理論上の最大スループットは 1 秒あたり 10 豚に達する可能性があることがわかります。

GC 構成例

2 つの異なる構成システムを使用したパフォーマンスを見てみましょう。構成に関係なく、アプリケーションは 8GB RAM を搭載したデュアルコア Mac (OS X10.9.3) 上で実行されます。

最初の構成:

1.4G ヒープ (-Xms4g -Xmx4g)
2. CMS を使用して古い世代をクリーンアップし (-XX:+UseConcMarkSoupGC)、並列コレクターを使用して新しい世代をクリーンアップします (-XX: +UseParNewGC)
3. ヒープ (-Xmn512m) の 12.5% を新しい世代に割り当て、Eden 領域と Survivor 領域のサイズが同じになるように制限します。
2 番目の構成は少し異なります:

1.2G ヒープ (-Xms2g -Xms2g)
2. 新世代と旧世代の両方で、並列 GC を使用します (-XX:+UseParallelGC)
3. ヒープの 75% を割り当てます。新世代 (-Xmn 1536m)
4. 今度は、どの構成がよりパフォーマンスが良いか賭けてみましょう (つまり、1 秒間に何頭の豚を食べることができるかということです)。最初の構成にチップを搭載した人はがっかりするでしょう。結果はまったく逆です。

1. 最初の構成 (大規模なヒープ、大規模な旧世代、CMS GC) は 1 秒あたり 8.2 個の豚を食べることができます
2. 2 番目の構成 (小さなヒープ、大規模な新世代、並列 GC) 1秒あたり9.2匹の豚を食べる

それでは、この結果を客観的に見てみましょう。割り当てられるリソースは 2 分の 1 に減少しますが、スループットは 12% 増加します。これは常識に反するため、何が起こっているのかをさらに分析する必要があります。

GC 結果を分析する

その理由は実際には複雑ではありません。答えを見つけるには、テストの実行時に GC が何を行っているかを注意深く確認するだけです。ここで、使用するツールを選択します。 jstat の助けを借りて、その背後にある秘密を発見しました。コマンドは大まかに次のようなものです:

jstat -gc -t -h20 PID 1s
ログイン後にコピー

データを分析すると、構成 1 で 1129 GC サイクル (YGCT_FGCT) が発生し、合計 63.723 秒かかったことがわかりました。 th 2 つの構成は合計 168 回 (YGCT+FGCT) 一時停止し、所要時間はわずか 11.409 秒でした。

Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
594.0 174720.0 174720.0 163844.1  0.0   174848.0 131074.1 3670016.0  2621693.5  21248.0 2580.9   1006   63.182  116 0.236   63.419
595.0 174720.0 174720.0 163842.1  0.0   174848.0 65538.0  3670016.0  3047677.9  21248.0 2580.9   1008   63.310  117 0.236   63.546
596.1 174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0   491772.9  21248.0 2580.9   1010   63.354  118 0.240   63.595
597.0 174720.0 174720.0  0.0   163840.1 174848.0 131074.1 3670016.0   688380.1  21248.0 2580.9   1011   63.482  118 0.240   63.723
ログイン後にコピー

したがって、両方の場合のワークロードが等しいことを考慮すると、この豚を食べる実験では、GC が寿命の長いオブジェクトを見つけられなかった場合、GC はガベージ オブジェクトをより速くクリーンアップできます。最初の構成では、GC 動作の頻度は 6 ~ 7 回程度、合計の一時停止時間は 5 ~ 6 回になります。

この話を伝えることには 2 つの目的があります。まず最も重要なことは、このけいれんするニシキヘビのことを頭から追い出したかったということです。もう 1 つのより明白な利点は、GC チューニングが非常に熟練した経験であり、基礎となる概念を完全に理解する必要があることです。この記事で使用されているものは非常に一般的なアプリケーションにすぎませんが、選択のさまざまな結果もスループットと容量計画に大きな影響を与えます。実際のアプリケーションでは、この違いはさらに大きくなります。したがって、これらの概念をマスターするか、日常業務に集中してニーズに最適な GC 構成を Plumbr に判断させるかはあなた次第です。

Java のスループットに対するガベージ コレクター GC の影響のテストに関連するその他の記事については、PHP 中国語 Web サイトに注目してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!