4

全て、

Solaris 10 の jrockit 64 ビット JVM (27.3.1) を使用して、WebLogic サーバー 9.2 MP2 で J2EE アプリケーションを実行しています。

use runtime.exec を呼び出して、jfmerge という実行可能ファイルを呼び出して PDF ドキュメントを作成します。

Solaris では、runtime.exec が呼び出されると、重複した JVM が一時的に生成され、jfmerge プロセスが開始されることがわかりました。これは非効率的ですが (私たちの JVM は 5 GB であるため、複製されたシェル JVM も 5 GB です)、主な問題は、アプリケーションでこの機能 (PDF 生成) に大きな負荷がかかると、複製された JVM が時々使用されるという事実にあります。決して出ません。

JVM がハングすると、重複する JVM 全体が 5 GB のプロセス サイズのすべてをディスク スワップに書き込むため、サーバーで大きな問題 (アプリケーションの極端な低速化とユーザー セッションの終了) が発生します。

次のハングしたスレッドは、プロセスが手動で強制終了されるまで、ハングした JVM プロセスと相関していることに注意してください。

"[STUCK] ExecuteThread: '17' for queue: 'weblogic.kernel.Default (self-tuning)'" id=3463 idx=0x158 tid=3460 prio=1 ネイティブで、jrockit/io/FileNativeIO のデーモンが動作しています。 readBytesPinned(Ljava/io/FileDescriptor;[BII)I (ネイティブ メソッド) jrockit/io/FileNativeIO.readBytes(FileNativeIO.java:30) で java/io/FileInputStream.readBytes([BII)I(FileInputStream.java) でjava/io/FileInputStream.read(FileInputStream.java:194) java/lang/UNIXProcess$DeferredCloseInputStream.read(UNIXProcess.java:227) で java/io/BufferedInputStream.fill(BufferedInputStream.java:218) java/io で/BufferedInputStream.read(BufferedInputStream.java:235) ^-- 保持ロック: gov/v3/common/formgeneration/sessionbean/FormsBean で java/io/BufferedInputStream@0xffffffffec6510470[シン ロック]。gov/v3/common/formgeneration/sessionbean/FormsBean.createPDF(FormsBean.java:750) で getProcessStatus(FormsBean.java:809) gov/v3/common/formgeneration/sessionbean/FormsBean.getTemplateDetails(FormsBean.java:450) でgov/v3/common/formgeneration/sessionbean/FormsBean.generateSinglePDF(FormsBean.java:1371) gov/v3/common/formgeneration/sessionbean/FormsBean.generatePDF(FormsBean.java:263) gov/v3/common/formgeneration /sessionbean/FormsBean.endorseDocument(FormsBean.java:2377) at gov/v3/common/formgeneration/sessionbean/Forms_qaco28_EOImpl.endorseDocument(Forms_qaco28_EOImpl.java:214) gov/v3/delegates/common/FormsAndNoticesDelegate.endorseDocument(FormsAndNoticesDelegate.java) :128) gov/v3/actions/common/EndorseDocumentAction.executeRequest(EndorseDocumentAction.java:68) gov/v3/fwk/controller/struts/action/V3CommonDispatchAction.dispatchToExecuteMethod(V3CommonDispatchAction.java:532) at gov/v3/fwk/controller/struts/action/V3CommonDispatchAction.executeBaseAction(V3CommonDispatchAction.java:336) at gov /v3/fwk/controller/struts/action/V3BaseDispatchAction.execute(V3BaseDispatchAction.java:69) at org/apache/struts/action/RequestProcessor.processActionPerform(RequestProcessor.java:484) at gov/v3/fwk/controller/struts /requestprocessor/V3TilesRequestProcessor.processActionPerform(V3TilesRequestProcessor.java:384) org/apache/struts/action/RequestProcessor.process(RequestProcessor.java:274) org/apache/struts/action/ActionServlet.process(ActionServlet.java:1482) ) org/apache/struts/action/ActionServlet.doGet(ActionServlet.java:507) gov/v3/fwk/controller/struts/servlet/V3ControllerServlet.doGet(V3ControllerServlet.java:110) で javax/servlet/http/HttpServlet.service(HttpServlet.java:743) で javax/servlet/http/HttpServlet でweblogic/servlet/internal/StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227) の .service(HttpServlet.java:856) weblogic/servlet/internal/StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125) の weblogic/servlet/ internal/ServletStubImpl.execute(ServletStubImpl.java:283) で weblogic/servlet/internal/ServletStubImpl.execute(ServletStubImpl.java:175) で weblogic/servlet/internal/WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3231) で weblogic /security/acl/internal/AuthenticatedSubject.doAs(AuthenticatedSubject.java:321) weblogic/security/service/SecurityManager.runAs(SecurityManager.java:121) で weblogic/servlet/internal/WebAppServletContext.securedExecute(WebAppServletContext.java:2002) で weblogic/servlet/internal/WebAppServletContext.execute(WebAppServletContext.java) :1908) weblogic/servlet/internal/ServletRequestImpl.run(ServletRequestImpl.java:1362) で weblogic/work/ExecuteThread.execute(ExecuteThread.java:209) で weblogic/work/ExecuteThread.run(ExecuteThread.java:181) でat jrockit/vm/RNI.c2java(JJJJJ)V(ネイティブ メソッド) -- トレースの終了java:1908) weblogic/servlet/internal/ServletRequestImpl.run(ServletRequestImpl.java:1362) で weblogic/work/ExecuteThread.execute(ExecuteThread.java:209) で weblogic/work/ExecuteThread.run(ExecuteThread.java:181) で) at jrockit/vm/RNI.c2java(JJJJJ)V(ネイティブ メソッド) -- トレースの終了java:1908) weblogic/servlet/internal/ServletRequestImpl.run(ServletRequestImpl.java:1362) で weblogic/work/ExecuteThread.execute(ExecuteThread.java:209) で weblogic/work/ExecuteThread.run(ExecuteThread.java:181) で) at jrockit/vm/RNI.c2java(JJJJJ)V(ネイティブ メソッド) -- トレースの終了

私たちはいくつかのことをしたいと思います:

1.) 単純な jfmerge 実行可能ファイルを実行するときにその機能を必要とせず、大量のオーバーヘッドが発生するため、重複する JVM の生成を防ぎます。

2.) 短期的には、少なくともこの重複した JVM が無期限に処理されるのを防ぎます。

4

3 に答える 3

6

この回答は遅れていますが、同じ問題があり、Solaris がメモリを管理する方法が問題です。

問題は、私の場合は 10 GB の大量のメモリを使用するアプリケーション サーバーがあり、単純な「ls」を実行したい場合、新しいプロセスを実行するには 10 GB が必要です。

Solaris はサーバーで利用可能な 10 GB の追加容量を必要とし、Linux は「コピー オン ライト」と呼ばれる機能を使用します。この機能により、新しいプロセスをフォークするオーバーヘッドが削減されます。

http://developers.sun.com/solaris/articles/subprocess/subprocess.html

歴史的背景と問題の説明

従来、Unix で新しいプロセスを作成する方法は 1 つしかありませんでした。fork() システム コールを使用し、その後に exec() システム コールを使用することがよくありました。fork() 呼び出しは親プロセスのアドレス空間全体のコピーを作成し、exec() はそのコピーを新しいプロセスに変換します。

(注: Solaris OS では、スワップ スペースという用語は、システム用に構成された物理メモリとディスク スワップ スペースの組み合わせを表すために使用されます。ただし、他の Unix システムでは、この用語は、バッキング ストアとも呼ばれるディスク上のスワップ スペースを意味する場合があります。混乱を避けるために、仮想メモリ (VM) という用語を使用して、物理メモリとディスク スワップ スペースを意味します。)

一般に、fork/exec メソッドは非常にうまく機能しています。ただし、正当な理由なくメモリが不足したり、フォークのパフォーマンスが低下したりするなど、場合によっては欠点があります。

メモリ不足: 大容量メモリ プロセスの場合、fork() は親メモリの 2 倍の量を必要とするため、VM の量が不十分なために fork() システム コールが失敗する可能性があります。これは、 fork() の直後に exec() 呼び出しが続き、余分なメモリのほとんどを解放する場合でも発生する可能性があります。これが発生すると、アプリケーションは通常終了します。

たとえば、64 ビット アプリケーションが現時点で 6 ギガバイト (Gbytes) の VM を消費しており、ls(1) コマンドを実行するサブプロセスを作成する必要があるとします。親プロセスは、その時点でさらに 6G バイトの VM が利用可能な場合にのみ成功する fork() 呼び出しを発行します。システムで使用可能な VM がそれほど多くない場合 (これはよくある状況です)、 fork() は ENOMEM で失敗します。明らかに、ls(1) コマンドを実行するのに 6G バイト近くのメモリは必要ありませんが、fork() はそれを知りません。

アプリケーションだけでなく、Sun 独自のツールも同じ問題に悩まされる可能性があります。たとえば、次の Sun RFE (拡張要求) が dbx に対して提出されました: 「4748951 dbx シェルは fork(2) ではなく非組み込みコマンドに posix_spawn() を使用する必要があります」。

RFE 4748951 は、顧客のユーティリティが dbx を呼び出して、dbx 内から cut(1) コマンドを実行する必要があるスクリプトを使用して巨大なコア ファイルを読み取ったときに発生しました。can't fork - try again というエラー メッセージが表示され、dbx が異常終了しました。調査の結果、dbx は fork/exec を使用してその小さな cut(1) コマンドを実行し、fork() 呼び出し中に VM を使い果たしたことが明らかになりました。

現在、Solaris Java Virtual Machine (JVM) も同じ問題に悩まされています。この Sun RFE で説明されているように、「5049299 Use posix_spawn, not fork, on S10 to avoid swap exportion」.


したがって、3 つのオプションがあります。

1.- 以前に Runtime.exec 関数を実行します。

2.- 他の Java サーバーとのプロセス間通信を作成し、そこで Runtime.exec 命令を実行します。

3.- システム C 関数を呼び出す JNI クラスを作成します。私はこのオプションを採用しましたが、完璧に機能します。

サンプルコードをここに置きます。

ジャバコード。

public class CallOS {
    static {
            System.loadLibrary("CallOS");
    }

    public native int exec(java.lang.String cmd);

    public static void main(String[] args) {
            int returnValue = 0;
            returnValue = new CallOS().exec("ls -la");
            System.out.println("- " + returnValue);
    }
}

C ヘッダー コード。これは javah -jni CallOS で生成されます

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CallOS */

#ifndef _Included_CallOS
#define _Included_CallOS
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     CallOS
 * Method:    exec
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_CallOS_exec
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

C コード。

#include "CallOS.h"
#include <stdlib.h>

JNIEXPORT jint JNICALL Java_CallOS_exec
  (JNIEnv *env, jobject obj, jstring cmd)
{
   jint  retval;
   jbyte *str;

   str = (*env)->GetStringUTFChars(env, cmd, NULL);
   if(str == NULL) return NULL;

   retval = system(str);

   (*env)->ReleaseStringUTFChars(env, cmd, str);
   return retval;
};

これがあなたのお役に立てば幸いです。

于 2010-05-31T23:40:05.240 に答える
0

生成されたプロセスを適切にstdout処理していますか? stderrブロックを確実に防止するには、両方を別々のスレッドで使用する必要があります。詳細については、この回答を参照してください。プロセスのスポーンが一部のジョブで適切に機能する場合もあれば、他のジョブで適切に機能する場合もあります (ハングを引き起こす stdout/err の量が原因で)。

forkプロセスの重複に関しては、JVM が/になることを期待しexecます。これにより、Java プロセス ( ) が複製forkされ、新しいプロセス ( ) に置き換えられexecます。それがあなたが見ているものなのだろうか?また、OSがCOW(コピーオンライト)を実装して、イメージ間で異なるメモリページのみを複製することを期待していることにも注意してください。したがって、通常の状況では、JVMの複製は、できるだけ多くのメモリを消費しません考える。

于 2009-07-21T18:02:48.150 に答える
0

ブライアンがほのめかしたように、UNIX では、別のプロセスが別のプログラムを開始する標準的な方法は、親プロセスと子プロセスに fork することです。次に、子プロセスは exec を呼び出して、自分自身を新しいプログラムに置き換えます。JVM は、jfmerge プログラムを開始するためにこれを行う必要があります。

通常、子プロセスのメモリ サイズは問題になりません。これは、OS がコピー オン ライトを使用して、子プロセスが exec を呼び出すまで 2 つのプロセスが同じメモリ イメージを共有できるようにするためです。子プロセスの JVM モデルでは、孫が jfmerge を実行し、子プロセスが孫を管理して、2 回 fork する必要がある可能性があります。これにより、重複した JVM プロセスが表示される理由が説明されます。スタック トレースは、プロセスが入力ストリームからの読み取りをブロックしたことを示しています。jfmerge の実​​行速度が遅く、jfmerge が何らかの出力を生成するのを待ってプロセスがハングアップしている可能性があります。

できることは、5GB JVM の代わりに、他のプロセスで jfmerge を起動することです。必要に応じて jfmerge を実行するスタンドアロン プログラムを作成し、何らかの形式のプロセス間通信を通じてメイン プロセスと通信させます。このスタンドアロンの jfmerge サーバーは、動作にそれほど多くのメモリを必要としないため、フォークされた子プロセスの影響はそれほど大きくありません。

于 2009-12-08T15:20:44.720 に答える