GCとページング、GCとソケットバッファ

OLD領域がCG前にページアウトする問題

問題の経緯

  • JVM上で動くサーバアプリケーション
  • CMS GCを使用
  • 状況によってOLD領域に行くオブジェクトがたまに出てくる
  • ゆえにOLD領域が埋まり切るまでは長い時間がかかる(フルGCはなかなか実施されない)
  • OSによりOLD領域がページアウトされる
  • フルGC実行時にページインがGBytes単位で発生する
  • アプリケーションが長時間フリーズする

Oracleのページにも書いてあった。

Tuning the Java Heap (Sun Java System Web Server 7.0 Update 8 Performance Tuning, Sizing, and Scaling Guide)

Excessive use of physical memory for Java heap may cause paging of virtual memory to disk during garbage collection, resulting in poor performance.

対策

一番簡単な対策はヒープサイズを縮小することだ。そもそもこの問題は不要に巨大なヒープを割り当てているため発生するものと考えていい。 だが残念ながら今回はたまに訪れる高負荷時に巨大なヒープが必要であり、かつ低負荷の状態が長期間続いてしまったようだ。

しかし対策は簡単である。下記のどちらか(両方でもいい)を実施しよう。

  • 許されるなら G1GC を使う(CPU使用率が上がるのを許容できるならば)
  • 許されるなら vm.swappiness を0*1にし物理メモリをたくさん積む

CPUリソースに余裕がないアプリケーションである場合、G1GC を使うと平時のスループットが落ちる可能性が高いが オブジェクトが頻繁にOLD領域へ移るのであれば有用な選択肢だろう。 もちろんこの辺りはアプリケーションの特性や負荷状況に依るのでチューニングしながら試行錯誤する必要はある。 とは言え本番環境と同じような負荷状況がテスト環境に再現できるとは限らないが…。

フルGCとソケットバッファ

フルGCに関して、別の(解決不能な)問題点についても書いておこう。

インバウンドに1Gbpsの通信が発生しているアプリケーションで0.5秒のフルGCが発生した場合、 ソケットバッファから溢れロストするパケットは果たしてどれだけか?これは wmem_max をいくら(現実的な範囲で)大きく指定しようが、 大量のパケット、おそらくは数十MBytes単位がロストするだろう。当然パケットロスに伴いスループットは劇的に悪化する可能性が高い *2が、 GCのある言語を使う以上、これは避けようがない。

月並な結論だが、アプリケーションに安定したスループットが必要ならばやはりC++やRustで作るしか無いようだ。

*1:物理メモリに余裕が無い場合はOOM Killerにプロセスを殺されやすくなるので、1~10程度が現実的かもしれない

*2:これはもしvegasのようなRTTベースの輻輳制御アルゴリズムを用いている場合であればある程度は平気なのだろうか