java - 如何在Java中正确使用ulimit?

标签 java memory jvm

我的Java程序必须在内存限制为指定数量的环境中运行。当我运行我的Java服务时,它在启动过程中将耗尽内存。

这是我正在使用的命令和我设置的值的示例:

ulimit -Sv 1500000
java \
    -Xmx1000m -Xms1000m \
    -XX:MaxMetaspaceSize=500m \
    -XX:CompressedClassSpaceSize=500m \
    -XX:+ExitOnOutOfMemoryError \
    MyClass

从理论上讲,我已经考虑了所有可以找到文档的内容。有堆(1000m)和元空间(500m)。但是在启动JVM时,它仍然会耗尽内存。当我将ulimit设置为比heap + metaspace大600mib时,就会发生这种情况。

我缺少什么类别的内存,以便可以适本地设置ulimit?

用例:我正在内存有限的Docker容器中运行任务。这意味着linux cgroups在做限制。当超出内存限制时,cgroup只能暂停或终止超出其范围的进程。我真的希望Java进程在出现问题时能够正常失败,并且它使用过多的内存,以便包装bash脚本可以将错误报告给任务启动器。

我们正在使用Java 8,因此我们需要担心元空间而不是permgen。

更新:它不会因OutOfMemoryError而死亡。这是错误:
Error occurred during initialization of VM
Could not allocate metaspace: 524288000 bytes

最佳答案

有效地限制Java确实很困难。许多池是无界的,并且当分配尝试失败时,JVM将发生灾难性的故障。并非实际上所有内存都已提交,而是保留了很多内存,因此计入了ulimit施加的虚拟内存限制。

经过大量研究,我发现了Java使用的许多不同类别的内存。此答案适用于64位系统上的OpenJDK和Oracle 8.x:



这是JVM内存中最容易理解的部分。这是您程序存储器的大部分被使用的地方。可以使用-Xmx-Xms选项对其进行控制。

元空间

这似乎包含有关已加载类的元数据。我无法确定这个类别是否会向操作系统释放内存,或者是否只会增加内存。默认最大值为 1g 。可以使用-XX:MaxMetaspaceSize选项进行控制。注意:如果没有指定压缩类空间,则指定此选项可能不会做任何事情。

压缩的类空间

这似乎与元空间有关。我无法确定这个类别是否会向操作系统释放内存,或者是否只会增加内存。默认最大值为 1g 。可以使用'-XX:CompressedClassSpaceSize`选项进行控制。

垃圾收集器开销

取决于所选的垃圾收集器,似乎有固定的开销量,并且根据堆的大小还有额外的分配。观察表明,此开销约为堆大小的5%。没有已知的限制此选项的选项(除了选择其他GC算法之外)。

线程

每个线程为其堆栈保留 1m 。 JVM似乎保留了额外的 50m 内存,作为防止堆栈溢出的安全措施。可以使用-Xss选项控制堆栈大小。安全尺寸无法控制。由于无法强制执行最大线程数,并且每个线程都需要一定数量的内存,因此该内存池在技术上是不受限制的。

Jar文件(和zip文件)

默认的zip实现将使用内存映射来访问zip文件。这意味着访问的每个jar和zip文件都将进行内存映射(要求保留的内存量等于文件大小的总和)。可以通过设置sun.zip.disableMemoryMapping系统属性(如-Dsun.zip.disableMemoryMapping=true)来禁用此行为。

NIO直接缓冲区

任何直接缓冲区(使用allocateDirect创建)将使用该数量的堆外内存。 NIO的最佳性能来自直接缓冲区,因此许多框架都将使用它们。

JVM无法限制NIO缓冲区允许的内存总量,因此该池在技术上是不受限制的。

此外,此内存在每个接触缓冲区的线程上都在堆上复制。有关更多详细信息,请参见this

由库分配的 native 内存

如果您正在使用任何 native 库,则它们分配的任何内存都将是堆外的。一些核心Java库(例如java.util.zip.ZipFile)也使用消耗堆内存的 native 库。

JVM没有提供任何方法来限制 native 库分配的内存总量,因此该池在技术上是不受限制的。

malloc竞技场

JVM使用malloc来处理许多这些 native 内存请求。为了避免线程争用问题,malloc函数使用多个预分配的池。池的默认数量等于8 x cpu,但是可以通过设置环境变量MALLOC_ARENAS_MAX来覆盖。每个池将保留一定数量的内存,即使它没有被全部使用。

通常建议在Java中将MALLOC_ARENAS_MAX设置为1-4,因为最频繁的分配是从堆完成的,并且较低的竞技场计数将防止浪费的虚拟内存计入ulimit。

从技术上讲,该类别不是它自己的池,但是它解释了额外内存的虚拟分配。

关于java - 如何在Java中正确使用ulimit?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44273737/

相关文章:

java - 如何让 Android 旋转器处于 Activity 状态?

java - 当测试文件未扩展到 TestCase 时,如何在 JUnit 4 中动态创建测试套件?

Java 多进程和堆大小

c# - 找到正确的基地址

java - 如何用 Java 编写正确的微基准测试?

java - 外部项目中的 Scala 密封类子类化

java - 是否有任何符合 IEEE754(r) 的 Java 实现?

java - 为什么我收到未报告的异常

java - 如何避免在 Java 中产生垃圾?

java - jcmd - 命令 ManagementAgent.start 的用途是什么?