Runtime.exec导致重复的JVM无限期挂起,直到被杀死(Solaris 10)

时间:2009-07-21 17:44:11

标签: solaris runtime.exec hung

3 个答案:

答案 0 :(得分: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()系统调用可能由于VM数量不足而失败,因为fork()需要两倍的父内存量。即使fork()紧跟一个exec()调用会释放大部分额外内存,也会发生这种情况。发生这种情况时,应用程序通常会终止。

例如,假设64位应用程序此刻正在消耗6千兆字节(GB),并且需要创建一个子进程来运行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执行那个tiny cut(1)命令,并在fork()调用期间用尽VM。

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


所以你有3个选择。

1.-先执行Runtime.exec函数。

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

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;
};

我希望对你有所帮助。

答案 1 :(得分:0)

您是否正确处理了衍生过程stdout / stderr?您需要在单独的线程中使用它们以可靠地防止阻塞。有关详细信息,请参阅this answer。可能是您的进程生成对某些作业和其他作业正常工作(由于stdout / err触发挂起的数量)。

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

答案 2 :(得分:0)

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

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

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