我有一个大的堆(高达240GB,但在这个执行阶段的大部分时间内在20-40GB范围内)JVM [1]在Linux [2]下在24核心的服务器上运行。我们有成千上万的对象必须由外部可执行文件处理。然后将这些可执行文件创建的数据加载回JVM。每个可执行文件产生大约半兆字节的数据(在磁盘上),当在进程完成后立即读取时,当然会更大。
我们的第一个实现是让每个可执行文件只处理一个对象。这涉及产生两倍于我们有对象的可执行文件(因为我们调用了一个调用可执行文件的shell脚本)。我们的CPU利用率将从高开始,但不一定是100%,并且慢慢恶化。当我们开始测量以查看发生的情况时,我们注意到过程创建时间[3]不断减慢。虽然从亚秒开始,但最终会花费一分钟或更长时间。可执行文件完成的实际处理通常不到10秒。
接下来,我们更改了可执行文件,以获取要处理的对象列表,以尝试减少创建的进程数。批量大小为几百(约为我们当前样本量的1%),过程创建时间约为2秒左右。长到5-6秒左右。
基本上,为什么在执行继续时创建这些流程需要这么长时间?
[1] Oracle JDK 1.6.0_22
[2]红帽企业Linux高级平台5.3,Linux内核2.6.18-194.26.1.el5#1 SMP
[3]创建ProcessBuilder对象,重定向错误流并启动它。
答案 0 :(得分:3)
我的猜测是你可能遇到fork / exec的问题,如果Java使用fork / exec系统调用来生成子进程。
通常fork / exec效率很高,因为fork()做的很少 - 所有页面都是copy-on-write。对于非常大的进程(即那些映射了千兆字节的进程),这种情况就不再那么正确了,因为页面表本身需要相对较长的时间来创建 - 当然还有销毁,因为你立即调用了exec。
当你使用大量的堆时,这可能会影响你。您映射的页面越多,可能变得越糟糕,这可能导致逐渐减速。
考虑:
注意:这都是猜测;你应该做一些实验,看看是否是这种情况。
答案 1 :(得分:1)
很可能你的资源不足。在创建这些进程时,您的磁盘是否变得更加繁忙。您是否确保流程少于核心流程? (最小化上下文切换)您的平均负载是否低于24?
如果您的CPU消耗正在下降,您可能会遇到IO(磁盘/网络)争用,即进程无法快速获取/写入数据以使其保持忙碌状态。如果你有24个核心,你有多少个磁盘?
我建议你每个CPU有一个进程(在你的情况下我想象4)给每个JVM六个任务同时运行以使用它的所有核心而不会使系统过载。
答案 2 :(得分:1)
使用一组长期存在的进程将数据从队列中拉出并将其发送回来不断为每个事件分配新进程,尤其是来自具有该巨大堆的主机JVM,你会好得多。
分割240GB图像不是免费的,它消耗了大量的虚拟资源,即使只是一秒钟。操作系统不知道新进程将知道多长时间,因此它必须做好准备,就好像整个进程将会存在很长时间一样,因此它会设置所有240GB的虚拟克隆,然后再用exec调用来删除它。
如果你有一个长期存在的过程,你可以通过一些队列机制结束对象(并且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要快得多。
答案 3 :(得分:0)
我最同意彼得。你最有可能遭受IO瓶颈。一旦你可以进行处理,操作系统也必须更加努力地完成琐碎的任务,因此会产生指数级的性能损失。
因此,“解决方案”可能是创建“消费者”流程,只能初始化一些流程;彼得建议每个CPU或更多。然后使用某种形式的IPC将这些对象“转移”到消费者流程。
您的“消费者”流程应该管理子流程创建;处理可执行文件,我认为你没有任何访问权限,这样你就不会使操作系统混乱得太多,而且“工作”将“最终”完成。