Java Runtime.exec()がメモリに関して安全なLinuxカーネル/ libcバージョンはどれですか?

StackOverflow https://stackoverflow.com/questions/209875

質問

職場でのターゲットプラットフォームの1つは、Linux(カーネル2.6.13、古いFedora Coreに基づくカスタムディストリビューション)を実行するリソースに制約のあるミニサーバーです。アプリケーションはJava(Sun JDK 1.6_04)で記述されています。 Linux OOMキラーは、メモリ使用量が160MBを超えるとプロセスを強制終了するように構成されています。高負荷時でも、アプリケーションは120MBを超えることはなく、アクティブな他のネイティブプロセスと一緒に、OOM制限内に十分に収まります。

ただし、Javaから外部プロセスを実行する標準的な方法であるJava Runtime.getRuntime()。exec()メソッドには、特にLinuxでの不幸な実装は、アドレス空間がコピーされるため、子プロセスが(一時的に)親プロセスと同じ量のメモリを必要とする原因となります。最終的な結果として、Runtime.getRuntime()。exec()を実行するとすぐに、OOMキラーによってアプリケーションが強制終了されます。

現在、この問題を回避するには、個別のネイティブプログラムですべての外部コマンドを実行し、ソケットを介してそのプログラムと通信します。これは最適ではありません。

この問題についてオンラインで投稿した後、これは" newer"では発生しないことを示すフィードバックLinuxのバージョン。コピーオンライトを使用してposix fork()メソッドを実装しているため、アドレス空間全体ではなく、必要なときに変更が必要なページのみをコピーすることを意味します。

私の質問:

  • これは本当ですか?
  • これはカーネル、libc実装、または完全にどこかにありますか?
  • fork()のcopy-on-writeが使用可能なkernel / libc / whateverのバージョンは何ですか?
役に立ちましたか?

解決

これは、* nix(およびlinux)が時間の夜明け(またはmmusの夜明けの少なくとも)以来機能してきた方法とほぼ同じです。

* nixesで新しいプロセスを作成するには、fork()を呼び出します。 fork()は、すべてのメモリマッピング、ファイル記述子などを使用して呼び出しプロセスのコピーを作成します。メモリマッピングはコピーオンライトで行われるため、(最適な場合)メモリは実際にコピーされず、マッピングのみがコピーされます。次のexec()呼び出しは、現在のメモリマッピングを新しい実行可能ファイルのマッピングに置き換えます。したがって、fork()/ exec()は新しいプロセスを作成する方法であり、それがJVMの使用方法です。

注意点は、ビジーなシステムでの巨大なプロセスです。親は、子exec()がコピーオンライトの原因で膨大な量のメモリをコピーする前に、しばらく実行し続ける可能性があります。 VMでは、メモリを移動してガベージコレクターを容易にし、さらにコピーを作成できます。

「回避策」あなたがすでにやったことを行うこと、新しいプロセスの生成を処理する外部軽量プロセスを作成すること、またはfork / execよりも軽量なアプローチを使用してプロセスを生成することです(Linuxにはありません)、とにかく変更が必要ですjvm自体)。 Posixはposix_spawn()関数を指定します。理論的には、呼び出しプロセスのメモリマッピングをコピーせずに実装できますが、Linuxではそうではありません。

他のヒント

まあ、Linuxのfork()はコピーオンライトで行われるので、神はいつ(少なくとも2.2.xカーネルにあり、199xのどこかにあったので)知っているので、私はこれが本当かどうか個人的に疑います。

OOMキラーは、不発(fe、実際にほとんどのメモリを割り当てたプロセスを強制終了する必要はありません)で、最後のリポートとしてのみ使用される、かなり粗雑な手段であると考えられているため、 160Mで起動するように設定した理由がわかりません。

メモリ割り当てに制限を課す場合、ulimitはOOMではなくあなたの友人です。

OOMをそのままにする(または完全に無効にする)こと、ulimitsを設定すること、この問題を忘れることです。

はい、これは絶対にLinuxの新しいバージョンにも当てはまります(64ビットRed Hat 5.2を使用しています)。約18か月間、サブプロセスの実行に問題がありましたが、質問を読み、確認するためのテストを実行するまで問題を把握できませんでした。

16コアの32 GBのボックスがあり、-Xms4gや-Xmx8gなどの設定でJVMを実行し、Runtime.exec()を使用して16スレッドでサブプロセスを実行すると、プロセスを高速に実行できません1秒あたり約20のプロセス呼び出しよりも多くなります。

簡単な「日付」でこれを試してくださいLinuxでは約10,000回コマンドを実行します。プロファイリングコードを追加して、何が起きているかを見ると、すぐに開始されますが、時間がたつにつれて遅くなります。

質問を読んだ後、メモリ設定を-Xms128mおよび-Xmx128mに下げてみることにしました。これで、プロセスは1秒あたり約80プロセスコールで実行されます。 JVMメモリ設定はすべて変更しました。

32個のスレッドで試したとしても、メモリを使い果たしたような方法でメモリを消費しているようには見えません。何らかの方法で余分なメモリを割り当てる必要があるだけであり、これにより重い起動(およびおそらくシャットダウン)コストが発生します。

とにかく、この振る舞いをLinuxまたはJVMでも無効にする設定が必要なようです。

1:はい。 2:これは2つのステップに分かれています。fork()などのシステムコールは、glibcによってカーネルにラップされます。システムコールのカーネル部分はkernel / fork.cにあります 3:わかりません。しかし、あなたのカーネルにはそれがあると思います。

32ビットボックスでメモリ不足が脅かされると、OOMキラーが作動します。私はこれで問題を抱えたことはありませんが、OOMを寄せ付けない方法があります。この問題は、OOM設定の問題である可能性があります。

Javaアプリケーションを使用しているため、64ビットLinuxへの移行を検討する必要があります。それは間違いなくそれを修正する必要があります。ほとんどの32ビットアプリは、関連するライブラリがインストールされている限り、64ビットカーネルで問題なく実行できます。

32ビットfedoraのPAEカーネルを試すこともできます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top