在工作中,我们的目标平台之一是运行Linux的资源受限小型服务器(内核2.6.13,基于旧的Fedora Core的自定义发行版)。该应用程序是用Java(Sun JDK 1.6_04)编写的。Linux OOM杀手配置为在内存使用量超过160MB时杀死进程。即使在高负载下,我们的应用程序也永远不会超过120MB,并且与其他活动的本机进程一起,我们也始终保持在OOM限制内。
但是,事实证明,Java Runtime.getRuntime()。exec()方法(一种从Java执行外部过程的规范方法)在Linux上具有一种特别不幸的实现,该实现导致生成的子进程(临时)需要相同数量的子进程。由于复制了地址空间,因此内存作为父进程。最终结果是,一旦执行Runtime.getRuntime()。exec(),我们的应用程序就会被OOM杀手杀死。
目前,我们通过一个单独的本机程序执行所有外部命令来解决此问题,并通过套接字与该程序进行通信。这不是最佳的。
在网上发布有关此问题的信息后,我得到了一些反馈,表明这不应在“较新”版本的Linux上发生,因为它们使用写时复制实现posix fork()方法,大概意味着它将仅复制需要的页面。在需要时修改而不是立即修改整个地址空间。
我的问题是:
这是* nix(和linux)自从时间曙光(或mmus黎明)以来一直工作的方式。
要在* nixes上创建新进程,请调用fork()。fork()使用其所有内存映射,文件描述符等创建调用过程的副本。内存映射是写时复制的,因此(在最佳情况下)实际上不会复制任何内存,而仅复制这些映射。接下来的exec()调用将当前的内存映射替换为新的可执行文件的映射。因此,fork()/ exec()是创建新进程的方式,而这正是JVM使用的方式。
需要注意的是,繁忙的系统上存在大量进程,父级可能会继续运行一会儿,然后子级exec()会导致大量内存被复制,从而导致写时复制。在VM中,内存可以移动很多,以方便垃圾收集器产生更多的副本。
“解决方法”是做您已经做的事情,创建一个外部轻量级进程来处理产生新进程的过程,或者使用比fork / exec更轻量级的方法来产生进程(而linux没有,并且无论如何都会这样做)需要更改jvm本身)。Posix指定posix_spawn()函数,从理论上讲,可以在不复制调用进程的内存映射的情况下实现posix_spawn()函数- 但在Linux上不是这样。