web-dev-qa-db-ja.com

Java 11でコンパイルした後のオフヒープリーク

Spring WebアプリのmavenビルドをJava 11にアップグレードした後、Javaプロセスからのメモリ消費が常に増加していることがわかります。

正常に動作します:Java 8 JDK +でビルドしますJava 11

リークあり:Java 11 +でビルド+ Java 11

リークは、ヒープダンプやネイティブメモリトラッキングでは確認できません。物理メモリ+スワップがいっぱいになり、プロセスがシステムによって強制終了されるまで、プロセスは増加し続けます。どのような問題がこの種の問題を引き起こしているのでしょうか?

6
Jeppz

Javaのコンパイラコードを少し掘り下げた後、Java 9で導入された興味深い変更が見つかりましたが、まだ気付いていませんでした。この変更により、コンパイルターゲットによって異なる動作が発生する可能性があります。

ほとんどの最適化はjavacの代わりにJITコンパイラーによって行われることが広く知られていますが、後者は依然としていくつかのコード最適化を行います。 Java 9の前に、これらの最適化の1つは、String連結をStringBuilder :: appendチェーンに変換することでした。Java 9以降、javacはinvokedynamicではなく、新しく導入されたJava.lang.invoke.StringConcatFactoryクラスを呼び出しますStringBuilder:append呼び出しに変換します。Java 8にコンパイルすると、javacは最適化されたバイトコードを生成しますが、Java 9にコンパイルすると、最適化が委任されます実行時に前述の組み込みクラスに。

対応する JEP 28 は、この変更に関する詳細を提供します。 JEP 280の1つの成功指標は、String連結パフォーマンスが低下してはならないことです。しかし JDK-822176 は潜在的なパフォーマンスの低下をすでに報告しています。バグエントリによると、Java 8にコンパイルされた文字列連結のコードは、Java 11uでコンパイルされた同じコードよりもJava 9または11。バグエントリはまだ解決されていないため、おそらくパフォーマンスが唯一の回帰ではありません。

1
rmunge

Java 11では、ForkJoinPoolクラスの動作が少し異なります。

最後に使用してからスレッドが終了するまでのデフォルトの経過時間は60秒です。 Java 8では、これは文書化されていませんでしたが、実際には2秒でハードコーディングされました。プールが大きすぎる場合、Java 8の実装では、プールの2秒後にアイドルスレッドが終了します。しかし、Java 9/11バージョンのクラスでは、クラスを数分間存続させます。

スレッドの数と寿命を比較します。未使用のスレッドは、アプリケーションの起動時やForkJoinPoolsオブジェクトの作成時に早く終了しない場合があるため、スレッドの有効期間を延長すると、メモリの問題が発生しやすくなります。

同様の問題については、次の質問を参照してください。 ForkJoinPool performance Java 8 vs 11

Java 9 a 新しいコンストラクタ で値を構成するために導入されました。Java 8コンパイルと同じ動作を得るには、 Java 9.にコンパイルする前に、keepAliveTimeを明示的に2秒に設定するか、ForkJoinPoolオブジェクトのサイズを減らす必要があります。

1
rmunge