2017-05-03

Java: CMS GC is endless

メモリが空いているのに CMS GC が掛かり続けることがある。といっても Full GC ではないので、CPU と GC ログを食うだけで運用上の問題はないのだが、はっきりした理由が分からないと安全だと言い切ることもできない。私もこれまでに何度か調べてきたが、ずっと原因が分からずにいた。

今は少しググれば有用な情報がたくさん得られる。いい時代になったもんだ。これらの情報が 2010 年以前にあれば、私もどれだけ楽ができたか知れない。しかし、これらの情報は今回の私の問題を解決しない。例えば nekop 氏によると、

CMSが開始されるトリガーは二つある。ひとつはOld領域の利用率がCMSInitiatingOccupancyFractionに到達した場合。もうひとつは今CMS走らせないと先にヒープ埋まっちゃうよね、という統計判断を元にしたトリガー。

CMSInitiatingOccupancyFraction の初期値は 92%。しかし繰り返すが、メモリは十分に空いている。ヒープを 50% しか使用していないのに、CMS GC が掛かり続けることの説明にはならない。じゃあもう 1 つの「統計判断」とは何なのか。

後者の統計については細かい話になるので省略する。知りたい人はソース嫁。

orz。うん、まあ、ね。私もかれこれ 5 年以上この現象を見てきているので、そろそろ億劫がらずにコードを読んでみる時期なのかも知れないな。

現場で動いているのは Java 6 なのだが、ソースコードを見つけられなかったので Java 7 を見てみる。しかしコードを見たからって、そんな簡単に解決するんだったら世話ないっての。

・・・1 時間後。

解決しちゃった。 :-D

bool CMSCollector::shouldConcurrentCollect() {

  // ...snip...

  // Otherwise, we start a collection cycle if either the perm gen or
  // old gen want a collection cycle started. Each may use
  // an appropriate criterion for making this decision.
  // XXX We need to make sure that the gen expansion
  // criterion dovetails well with this. XXX NEED TO FIX THIS
  if (_cmsGen->should_concurrent_collect()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print_cr("CMS old gen initiated");
    }
    return true;
  }

  // ...snip...

  if (CMSClassUnloadingEnabled && _permGen->should_concurrent_collect()) {
    bool res = update_should_unload_classes();
    if (res) {
      if (Verbose && PrintGCDetails) {
        gclog_or_tty->print_cr("CMS perm gen initiated");
      }
      return true;
    }
  }
  return false;
}

重要なのは後半の部分。つまり、通常のヒープ領域だけでなく、Permanent 領域も CMS のトリガーになる ということ。この閾値は CMSInitiatingPermOccupancyFraction で、やはり初期値は 92%。

これで全てに納得がいった。これはつまり、Permanent 領域の使用量を把握して MaxPermSize を最適化しているほど、この現象に遭いやすくなるということだ。そういう場合、この値は限りなく 100% に近づけるのが正しい。(でなければ MaxPermSize を増やす)

-XX:CMSInitiatingPermOccupancyFraction=99

CMS でも、どうせ偶に concurrent mode failure や promotion failed 由来の Full GC は起こる(このとき Permanent 領域も GC される)し、Permanent 領域の変動がよほど大きいアプリケーションでもなければ、これで良いと思う。

しかしこの Permanent 領域の CMS トリガーは、もっと有名になって良いと思う。少なくとも、CMSInitiatingOccupancyFraction を出す際は、合わせて CMSInitiatingPermOccupancyFraction についても触れるべき。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。