Archive for January 18th, 2009
如何减小创建子进程的时使用的内存
项目中的一个程序,最近频繁报:can’t allocate memory的错误。研究后发现,原因是程序中调用了Runtime.getRuntime().exec()的缘故。遂Google之,发现类似问题很普遍。
下面的一篇文章很值得参考,顺便翻译了一下,希望对大家有所帮助,原文在这里
翻译如下:
如何减小创建子进程的时使用的内存(Minimizing Memory Usage for Creating Application Subprocesses)
作者:Greg Nakhimovsky
翻译:Charry
摘要:本文阐述了如何在Solaris下,为一个耗内存的应用程序创建一个子进程,而不引起内存不足或者死锁的问题。同时也介绍了一些相关话题,比如在Solaris下,系统如果给应用程序commit一块内存,以及其他操作系统(如Linux)如何实现类似的操作。
背景知识和问题描述
通常,在Unix下只有一种方式创建一个子进程:使用fork()系统调用,该函数调用后,通常会紧接着调用exec()。调用fork()会创建一份父进程地址空间的完全copy,然后exec()将这个copy变成一个新的进程。
(注:在Solaris下,术语“交换空间”指的是物理内存和磁盘交换空间的组合。然而,在其他操作系统中,这个术语只表示磁盘上的交换空间,也叫:后备存储(backing store)。所以为了避免混淆,我将使用术语“虚拟内存(VM)”表示物理内存加磁盘交换空间。)
一般情况下,fork/exec用起来没有什么问题。然而,在某些情况下,它有些缺点,比如:导致内存耗尽、fork的性能低下等。
内存耗尽:对于一个自身耗用大量内存的进程,fork()调用可能会失败,原因是没有足够的VM,因为fork()后内存使用会加倍。即使exec()马上被调用,然后释放多余的内存,这种情况(内存耗尽)仍然不可避免。当它发生时,应用程序通常会被终止。
举个例子,假如一个64位的程序在某时刻占用了6G的VM,它需要创建一个子进程,该子进程执行ls命令。父进程能成功创建子进程的条件是:在那个时刻,系统还有另外的6G的VM可供使用。如果系统不能提供那么多的内存(这种情况很常见),fork()将会调用失败,并返回ENOMEM。很明显ls命令并不需要6G的内存,但是fork()不知道这些。
除了普通应用程序,SUN自己的工具也饱受次问题折磨,比如下面的针对dbx的SUN RFE(优化请求)已经被提交,该请求指出:对于非内置命令,dbx shell应该用posix_spawn()代替,而不是用fork()。
REF4748951的一个实例:当一个工具包(utility)调用dbx去读取一个超大文件时,dbx内部又使用一个脚本去调用cut命令。这种情况下,dbx会报错:“无法创建子进程,请重试”,然后就退出了。有资料显示:dbx使用的fork/exec去执行那个小命令cut,然后在fork()调用中耗尽了内存。
Solaris版本的Java虚拟机目前也首次问题拖累(注:该文章写于2006年),比如在SUN REE 5049299描述到:在S10上使用posix_spawn而不是fork会避免交换空间耗尽。
Fork的性能:fork()会影响性能,即使多年来人们用COW(copy-on-write),“写时拷贝”的技术来优化fork(),我们仍然需要从父进程拷贝一定数量的数据到子进程。但是这个拷贝并非是必需的,比如:fork出来的子进程立即被exec()替换为一个新的进程。当父进程包含很多“内存映射区域”的时候,这种性能问题会显得尤为严重。
这些缺点,比如:内存耗尽、Fork性能低下,在下列情况下,会变得尤为重要:
- 在父进程占用大量内存时(而一个进程占用大量的内存在近几年来,变得越来越越普遍)
- Solaris中请求VM的内存commitment时
请看下面有关内存commitment的讨论。