从内核/ libc版本到Java Runtime.exec()的内存安全吗?

Bor*_*zic 23 java linux memory out-of-memory

在工作中,我们的一个目标平台是运行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的"更新"版本上,因为它们使用copy-on-write实现了posix fork()方法,可能意味着它只会复制它需要的页面在需要时修改而不是立即修改整个地址空间.

我的问题是:

  • 这是真的?
  • 这是内核,libc实现还是完全在其他地方?
  • 从哪个版本的kernel/libc/copy for-copy for fork()可用?

nos*_*nos 11

这几乎就是*nix(和linux)自从时间开始以来的工作方式(或者说是mmus的曙光).

要在*nixes上创建新进程,请调用fork().fork()创建一个调用进程的副本及其所有内存映射,文件描述符等.内存映射是写入时复制的,因此(在最佳情况下)实际上没有复制内存,只有映射.以下exec()调用将当前内存映射替换为新可执行文件的映射.因此,fork()/ exec()是您创建新进程的方式,也就是JVM使用的方式.

需要注意的是,繁忙的系统上存在大量进程,父进程可能会继续运行一段时间,因为exec()会导致大量内存被复制,导致写入时复制.在VM中,内存可以移动很多,以方便垃圾收集器产生更多的复制.

"解决方法"是做你已经完成的事情,创建一个外部轻量级进程来处理产生新进程 - 或者使用比fork/exec更轻量级的方法来生成进程(linux没有 - 而且无论如何需要改变jvm本身).Posix指定了posix_spawn()函数,理论上它可以在不复制调用进程的内存映射的情况下实现 - 但在linux上它不是.


ADE*_*Ept 5

好吧,我个人怀疑这是真的,因为Linux的fork()是通过copy-on-write完成的,因为上帝知道什么时候(至少2.2.x内核有它,它在199x的某个地方).

由于OOM杀手被认为是一种相当粗糙的仪器,已知会失火(fe,它不必杀死实际分配大部分内存的过程)并且应该仅用作最后一个resport,因此不清楚为什么你把它配置为在160M上发射.

如果你想对内存分配施加限制,那么ulimit是你的朋友,而不是OOM.

我的建议是单独留下OOM(或完全禁用它),配置ulimits,并忘记这个问题.