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,因此复制的 shell 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(Native Method) at jrockit/io/FileNativeIO.readBytes(FileNativeIO.java:30) at java/io/FileInputStream.readBytes([BII)I(FileInputStream.java) at java/io/FileInputStream.read(FileInputStream.java:194) at java/lang/UNIXProcess$DeferredCloseInputStream.read(UNIXProcess.java:227) at java/io/BufferedInputStream.fill(BufferedInputStream.java:218) at java/io /BufferedInputStream.read(BufferedInputStream.java:235) ^-- 持有锁:java/io/BufferedInputStream@0xfffffffec6510470[thin lock] at gov/v3/common/formgeneration/sessionbean/FormsBean。getProcessStatus(FormsBean.java:809) 在 gov/v3/common/formgeneration/sessionbean/FormsBean.createPDF(FormsBean.java:750) 在 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) at 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.executeBaseAction(V3CommonDispatchAction.java:336) 在 gov/v3/fwk/controller/struts/action/V3CommonDispatchAction.dispatchToExecuteMethod(V3CommonDispatchAction.java:532) /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 .service(HttpServlet.java:856) 在 weblogic/servlet/internal/StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227) 在 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)在 jrockit/vm/RNI.c2java(JJJJJ)V(Native Method) -- 跟踪结束java:1908) 在 weblogic/servlet/internal/ServletRequestImpl.run(ServletRequestImpl.java:1362) 在 weblogic/work/ExecuteThread.execute(ExecuteThread.java:209) 在 weblogic/work/ExecuteThread.run(ExecuteThread.java:181 ) 在 jrockit/vm/RNI.c2java(JJJJJ)V(Native Method) -- 跟踪结束java:1908) 在 weblogic/servlet/internal/ServletRequestImpl.run(ServletRequestImpl.java:1362) 在 weblogic/work/ExecuteThread.execute(ExecuteThread.java:209) 在 weblogic/work/ExecuteThread.run(ExecuteThread.java:181 ) 在 jrockit/vm/RNI.c2java(JJJJJ)V(Native Method) -- 跟踪结束

我们想做几件事:

1.) 防止产生重复的 JVM,因为我们在执行简单的 jfmerge 可执行文件时不需要它的任何功能,它会产生大量开销。

2.) 至少在短期内防止这个重复的 JVM 无限期地移交。

4

3 回答 3

6

这个答案迟了,但我们有同样的问题,对我们来说问题是 Solaris 如何管理内存。

问题是当我们有一个应用程序服务器,在我的例子中使用大量内存 10GB,并且我们想运行一个简单的“ls”,新进程需要 10GB 才能运行。

Solaris 需要我们服务器中可用的 10GB 额外空间,Linux 使用称为“写时复制”的功能此功能减少了分叉新进程的开销

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

历史背景和问题描述

传统上,Unix 只有一种方法来创建新进程:使用 fork() 系统调用,然后通常是 exec() 系统调用。fork() 调用复制整个父进程的地址空间,然后 exec() 将该副本转换为新进程。

(注意:在 Solaris OS 中,术语交换空间用于描述为系统配置的物理内存和磁盘交换空间的组合。但是,对于其他 Unix 系统,该术语可能意味着磁盘上的交换空间,也称为后备存储. 为避免混淆,我将使用术语虚拟内存 (VM) 来表示物理内存加上磁盘交换空间。)

通常,fork/exec 方法运行良好。但是,它在某些情况下也有缺点,例如无故耗尽内存和 fork 性能不佳。

内存不足:对于大内存进程,fork() 系统调用可能会由于 VM 数量不足而失败,因为 fork() 需要双倍的父内存量。即使在 fork() 之后立即执行 exec() 调用会释放大部分额外内存,也会发生这种情况。发生这种情况时,应用程序通常会终止。

例如,假设一个 64 位应用程序目前正在消耗 6 GB 的 VM,它需要创建一个子进程来运行 ls(1) 命令。父进程发出一个 fork() 调用,仅当此时还有另外 6 GB 的 VM 可用时才会成功。如果系统没有那么多可用的 VM(这种情况很常见),fork() 将失败并显示 ENOMEM。显然,ls(1) 命令不需要任何接近 6 GB 的内存即可运行,但 fork() 不知道这一点。

不仅应用程序,而且 Sun 自己的工具也会遇到同样的问题。例如,已为 dbx 提交了以下 Sun RFE(增强请求):“4748951 dbx shell 应该对非内置命令使用 posix_spawn() 而不是 fork(2)”。

RFE 4748951 出现在客户的实用程序调用 dbx 以使用脚本读取一个巨大的核心文件时,该脚本还需要从 dbx 中运行 cut(1) 命令。他们收到了无法分叉 - 重试导致 dbx 中止的错误消息。一项调查显示,dbx 使用 fork/exec 来执行那个微小的 cut(1) 命令,并在 fork() 调用期间耗尽了 VM。

Solaris Java 虚拟机 (JVM) 目前也遇到了同样的问题,如 Sun RFE 中所述:“5049299 在 S10 上使用 posix_spawn,而不是 fork,以避免交换耗尽”。


所以你有3个选择。

1.- 提前执行 Runtime.exec 函数。

2.-创建与其他java服务器的进程间通信,并在那里执行Runtime.exec instruccion。

3.- 创建一个 JNI 类来调用系统 C 函数。我选择了这个选项,而且效果很好。

我把我的示例代码放在这里。

Java 代码。

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

正如 Brian 暗示的那样,在 unix 上,另一个进程启动另一个程序的标准方法是分叉成一个父进程和一个子进程。然后子进程调用 exec 以用新程序替换自己。JVM 必须这样做才能启动您的 jfmerge 程序。

通常,子进程的内存大小不是问题,因为操作系统使用写时复制让两个进程共享相同的内存映像,直到子进程调用 exec。可能是 JVM 的子进程模型要求它分叉两次,孙子进程执行 jfmerge,子进程管理孙子进程。这可以解释为什么您会看到一个重复的 JVM 进程。堆栈跟踪显示了一个进程被阻止从输入流中读取。可能是 jfmerge 运行缓慢,进程只是挂起等待 jfmerge 产生一些输出。

您可以做的是让其他进程启动 jfmerge,而不是 5GB JVM。编写一个仅按需运行 jfmerge 的独立程序,并让它通过某种形式的进程间通信与主进程通信。这个独立的 jfmerge 服务器不需要太多内存来运行,因此分叉子进程的影响不会那么大。

于 2009-12-08T15:20:44.720 回答
0

您是否正确处理了生成的进程stdoutstderr您需要在单独的线程中使用这两者以可靠地防止阻塞。有关详细信息,请参阅此答案。您的进程生成可能适用于某些作业和其他作业(由于触发挂起的 stdout/err 的数量)。

关于重复进程的主题,我希望 JVM 能够fork/ exec。这会复制 Java 进程 ( fork),然后它应该用新进程 ( exec) 替换它。我想知道这就是你所看到的吗?另请注意,我希望操作系统实现 COW(写入时复制)仅复制图像之间不同的那些内存页面,因此在正常情况下,JVM 的复制不会消耗尽可能多的内存可能思考。

于 2009-07-21T18:02:48.150 回答