JVM がセーフポイントに到達できない場合に Java スタックを取得する方法 質問する

JVM がセーフポイントに到達できない場合に Java スタックを取得する方法 質問する

最近、実稼働 JVM の 1 つがランダムにフリーズするという状況が発生しました。Java プロセスは CPU を消費していましたが、目に見えるアクティビティはすべて停止しました。ログ出力はなく、GC ログには何も書き込まれず、ネットワーク要求にも応答しませんでした。プロセスは再起動されるまでこの状態のままでした。

org.mozilla.javascript.DToA クラスは、特定の入力で呼び出されると混乱し、非常に大きな値 (例: 5^2147483647) で BigInteger.pow を呼び出し、JVM がフリーズする原因になることが判明しました。おそらく java.math.BigInteger.multiplyToLen 内の大きなループが、ループ内でセーフポイント チェックを行わずに JIT 化されたのではないかと思います。次に JVM がガベージ コレクションのために一時停止する必要が生じたとき、BigInteger コードを実行しているスレッドがセーフポイントに到達するまでに非常に長い時間がかかるため、JVM はフリーズします。

質問です。今後、このようなセーフポイントの問題をどのように診断すればよいのでしょうか。kill -3 では何も出力されませんでした。正確なスタックを生成するためにセーフポイントに依存していると思われます。セーフポイントを待たずに実行中の JVM からスタックを抽出できる、本番環境で安全なツールはありますか。(この場合、BigInteger.pow が呼び出された直後、JVM を完全に妨害するほど大きな入力に達する前に、幸運にもスタック トレースのセットを取得できました。この幸運がなければ、問題を診断できたかどうかわかりません。)

編集: 次のコードは問題を示しています。

// Spawn a background thread to compute an enormous number.
new Thread(){ @Override public void run() {
  try {
    Thread.sleep(5000);
  } catch (InterruptedException ex) {
  }
  BigInteger.valueOf(5).pow(100000000);
}}.start();

// Loop, allocating memory and periodically logging progress, so illustrate GC pause times.
byte[] b;
for (int outer = 0; ; outer++) {
  long startMs = System.currentTimeMillis();
  for (int inner = 0; inner < 100000; inner++) {
    b = new byte[1000];
  }

  System.out.println("Iteration " + outer + " took " + (System.currentTimeMillis() - startMs) + " ms");
}

これにより、バックグラウンド スレッドが起動され、5 秒間待機してから膨大な BigInteger 計算が開始されます。フォアグラウンドでは、100,000 個の 1K ブロックの連続した割り当てが繰り返し行われ、100 MB の連続ごとに経過時間が記録されます。5 秒間に、MacBook Pro では 100 MB の連続ごとに約 20 ミリ秒で実行されます。BigInteger 計算が開始されると、長い一時停止が交互に現れ始めます。あるテストでは、一時停止は 175 ミリ秒、997 ミリ秒、2927 ミリ秒、4222 ミリ秒、22617 ミリ秒と連続していました (この時点でテストを中止しました)。これは、BigInteger.pow() が、セーフポイントに到達するまでの時間が徐々に長くなる、より大きな乗算操作を連続して呼び出していることと一致しています。

ベストアンサー1

あなたの問題には非常に興味をそそられました。JIT についてはあなたの言う通りでした。最初は GC タイプを試してみましたが、効果はありませんでした。次に JIT を無効にしてみたところ、すべてうまくいきました。

java -Djava.compiler=NONE Tests

次に、JIT コンパイルを出力しました。

java -XX:+PrintCompilation Tests

そして、BigInteger クラスでいくつかのコンパイルを行った後に問題が発生することに気づいたので、メソッドを 1 つずつコンパイルから除外しようとしたところ、最終的に原因が見つかりました。

java -XX:CompileCommand=exclude,java/math/BigInteger,multiplyToLen -XX:+PrintCompilation Tests

大きな配列の場合、この方法は長くかかる可能性があり、問題は実際にはセーフポイントにある可能性があります。何らかの理由でセーフポイントは挿入されませんが、コンパイルされたコードでも挿入されるはずです。バグのようです。次のステップはアセンブリコードを分析する、まだやってないです。

おすすめ記事