减缓Java下的进程创建?

减缓Java下的进程创建?,java,linux,performance,Java,Linux,Performance,我有一个在Linux[2]下运行的JVM[1],它在一个有24个内核的服务器上运行(最高可达240GB,但在执行的大部分阶段都在20-40GB的范围内)。我们有成千上万的对象必须由外部可执行文件处理&然后将这些可执行文件创建的数据加载回JVM。每个可执行文件产生大约半兆字节的数据(在磁盘上),当进程完成后直接读入时,数据当然会更大 我们的第一个实现是让每个可执行文件只处理一个对象。这涉及到生成的可执行文件数量是对象数量的两倍(因为我们调用了一个称为可执行文件的shell脚本)。我们的CPU利用率

我有一个在Linux[2]下运行的JVM[1],它在一个有24个内核的服务器上运行(最高可达240GB,但在执行的大部分阶段都在20-40GB的范围内)。我们有成千上万的对象必须由外部可执行文件处理&然后将这些可执行文件创建的数据加载回JVM。每个可执行文件产生大约半兆字节的数据(在磁盘上),当进程完成后直接读入时,数据当然会更大

我们的第一个实现是让每个可执行文件只处理一个对象。这涉及到生成的可执行文件数量是对象数量的两倍(因为我们调用了一个称为可执行文件的shell脚本)。我们的CPU利用率一开始会很高,但不一定是100%,然后慢慢恶化。当我们开始测量以了解发生了什么时,我们注意到流程创建时间[3]不断变慢。虽然从亚秒级开始,但最终需要一分钟或更长时间。可执行文件完成的实际处理通常不到10秒

接下来,我们更改了可执行文件,以获取要处理的对象列表,以减少创建的进程数。批量大小只有几百个(约为当前样本大小的1%),流程创建时间从2秒左右开始,增长到5-6秒左右

基本上,为什么随着执行的继续,创建这些流程要花这么长时间

[1] Oracle JDK 1.6.0_22
[2] Red Hat Enterprise Linux高级平台5.3,Linux内核2.6.18-194.26.1.el5#1 SMP

[3] 创建ProcessBuilder对象,重定向错误流并启动它。

最有可能的情况是资源耗尽。创建这些进程时,磁盘是否变得越来越繁忙。您是否确保您的进程少于核心?(尽量减少上下文切换)您的平均负载是否低于24

如果您的CPU消耗量下降,您可能会遇到IO(磁盘/网络)争用,即进程无法以足够快的速度获取/写入数据以保持忙碌。如果您有24个内核,您有多少个磁盘


我建议您每个CPU有一个进程(在您的例子中,我设想为4个),让每个JVM同时运行六个任务,以使用其所有内核,而不会使系统过载。

我非常同意Peter的观点。您的应用程序很可能受到IO瓶颈的影响。一旦您完成了may进程,操作系统也必须为琐碎的任务付出更大的努力,因此会有指数级的性能损失

因此,“解决方案”可以是创建“消费者”流程,只初始化某些流程;正如Peter建议的,每个CPU一个或多个。然后使用某种形式的IPC将这些对象“传输”到使用者进程


您的“消费者”流程应管理子流程的创建;我认为您没有任何访问权限的处理可执行文件,这样您就不会在操作系统中塞满太多的可执行文件,“作业”将“最终”完成。

我猜想,如果Java使用fork/exec系统调用生成子进程,您可能会遇到fork/exec的问题

通常,fork/exec是相当有效的,因为fork()做的很少——所有页面都是写时复制的。对于非常大的进程(即那些映射了千兆字节页面的进程),情况就不再如此了,因为创建页面表本身需要相对较长的时间,当然还有销毁,正如您立即调用exec的那样

由于您正在使用大量堆,这可能会影响您。您映射的页面越多,它可能变得越糟糕,这可能是导致渐进式减速的原因

考虑以下两种情况之一:

  • 如果libc中的fork/exec没有实现posix_spawn,则使用posix_spawn
  • 使用负责创建/收获其他子流程的单个子流程;生成一次,并使用一些IPC(管道等)来告诉它该做什么

注:这都是猜测;您可能应该做一些实验,看看情况是否如此。

您最好使用一组长寿命的进程,将数据从队列中拉出来,并将它们发送回队列,从而不断为每个事件派生新的进程,特别是从具有巨大堆的主机JVM

分叉240GB映像不是免费的,它会消耗大量虚拟资源,即使只是一秒钟。操作系统不知道新进程将感知多长时间,因此它必须做好准备,就好像整个进程都将是长寿命的一样,因此,在使用exec调用将其删除之前,它会设置所有240GB的虚拟克隆

如果您有一个长寿命的流程,可以通过某种队列机制(Java和C等都有很多)将对象终止到该流程,这将减轻分叉流程的一些压力

我不知道您是如何将数据从JVM传输到外部程序的。但是,如果您的外部程序可以使用stdin/stdout,那么(假设您使用的是unix),您可以利用inetd。在这里,您可以在inetd配置文件中为您的进程创建一个简单的条目,并为其分配一个端口。然后打开一个套接字,将数据倒进其中,然后从该套接字读回数据。Inetd为您处理网络细节,您的程序与stdin和stdout一样简单。请注意,网络上有一个开放的套接字,这在部署中可能不安全,也可能不安全。但是,设置通过网络服务运行的遗留代码也非常简单

您可以使用这样一个简单的包装器:

#!/bin/sh
infile=/tmp/$$.in
outfile=/tmp/$$.out

cat > $infile
/usr/local/bin/process -input $infile -output $outfile
cat $outfile
rm $infile $outfile

它并不是世界上性能最高的服务器,它的设计目标是处理数以百万计的事务,但它肯定比一次又一次地分叉240GB要快得多。

@oconnor0:omg。。。我也遇到了同样的问题,我真的开始怀疑:我们是都忘了发布资源还是哪里有bug?我实例化了很多外部流程,我注意到它们随着时间的推移变得越来越慢。如果你找到你