23

作業中のターゲット プラットフォームの 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 キラーによって強制終了されます。

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

この問題についてオンラインで投稿した後、Linux の「新しい」バージョンでは、コピー オン ライトを使用して posix fork() メソッドを実装しているため、これは発生しないはずであるというフィードバックを受け取りました。おそらく、必要なページのみをコピーすることを意味します。アドレス空間全体をすぐに変更するのではなく、必要なときに変更します。

私の質問は次のとおりです。

  • これは本当ですか?
  • これはカーネル、libc 実装、または完全に別の場所にあるものですか?
  • fork() のコピー オン ライトは、どのバージョンのカーネル/libc/whatever から利用できますか?
4

4 に答える 4

11

これは、*nix (および Linux) が黎明期 (または mmus の黎明期) から機能してきた方法とほとんど同じです。

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

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

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

于 2009-12-16T14:25:43.813 に答える
5

Linux の fork() はコピー オン ライトを介して実行されるため、個人的にはこれが真実であるかどうかは疑問です (少なくとも、2.2.x カーネルにはそれがあり、199x のどこかでした)。

OOM キラーは、失火することが知られているかなり粗雑な手段であると考えられており (fe、実際にメモリの大部分を割り当てたプロセスを強制終了する必要はありません)、最後のレポートとしてのみ使用する必要があります。なぜ160Mで起動するように設定したのですか。

メモリ割り当てに制限を課したい場合は、OOM ではなく、ulimit が役に立ちます。

私のアドバイスは、OOM をそのままにして (または完全に無効にして)、ulimits を構成し、この問題を忘れることです。

于 2008-10-16T22:05:40.177 に答える
2

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

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

これを Linux の単純な「date」コマンドで約 10,000 回試してください。何が起こっているかを監視するためにプロファイリング コードを追加すると、すぐに開始されますが、時間の経過とともに遅くなります。

あなたの質問を読んだ後、メモリ設定を -Xms128m と -Xmx128m に下げてみることにしました。現在、プロセスは 1 秒あたり約 80 回のプロセス コールで実行されています。私が変更したのは JVM メモリ設定だけでした。

32 スレッドで試しても、メモリ不足になるほどメモリを消費しているようには見えません。何らかの方法で追加のメモリを割り当てる必要があるだけで、起動 (およびシャットダウン) コストが高くなります。

とにかく、この動作を無効にする設定が Linux または JVM でさえあるべきだと思われます。

于 2009-12-16T14:07:18.340 に答える
1

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

OOM キラーは、32 ビット ボックスでメモリ不足が脅かされると発生します。これで問題が発生したことはありませんが、OOM を寄せ付けないようにする方法はいくつかあります。この問題は、OOM 構成の問題である可能性があります。

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

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

于 2008-10-16T21:02:01.537 に答える