一尘不染

关于内存,Java Runtime.exec()从哪个Linux内核/ libc版本安全?

java

在工作中,我们的目标平台之一是运行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()方法,大概意味着它将仅复制需要的页面。在需要时修改而不是立即修改整个地址空间。

我的问题是:

  • 这是真的?
  • 这是内核,libc实现还是其他地方的东西?
  • 从哪个版本的内核/ libc / fork()可以进行写时复制?

阅读 208

收藏
2020-12-03

共1个答案

一尘不染

这是* nix(和linux)自从时间曙光(或mmus黎明)以来一直工作的方式。

要在*
nixes上创建新进程,请调用fork()。fork()使用其所有内存映射,文件描述符等创建调用过程的副本。内存映射是写时复制的,因此(在最佳情况下)实际上不会复制任何内存,而仅复制这些映射。接下来的exec()调用将当前的内存映射替换为新的可执行文件的映射。因此,fork()/
exec()是创建新进程的方式,而这正是JVM使用的方式。

需要注意的是,繁忙的系统上存在大量进程,父级可能会继续运行一会儿,然后子级exec()会导致大量内存被复制,从而导致写时复制。在VM中,内存可以移动很多,以方便垃圾收集器产生更多的副本。

“解决方法”是做您已经做的事情,创建一个外部轻量级进程来处理产生新进程的过程,或者使用比fork /
exec更轻量级的方法来产生进程(而linux没有,并且无论如何都会这样做)需要更改jvm本身)。Posix指定posix_spawn()函数,从理论上讲,可以在不复制调用进程的内存映射的情况下实现posix_spawn()函数-
但在Linux上不是这样。

2020-12-03