2017-05-31 30 views
2

我的java程序必须运行在内存被限制到指定数量的环境中。当我运行我的java服务时,它在启动过程中内存不足。如何正确使用java的ulimit?

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

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

从理论上讲,我已经占了所有我能找到的文档。有堆(1000米)和元空间(500米)。但是在启动JVM时,它仍然耗尽内存。直到我将ulimit设置为比堆+元空间大600mib时才会发生这种情况。

我错过了什么类别的记忆,以便我可以适当地设置ulimit?

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

我们使用java 8,所以我们需要担心metaspace而不是permgen。

更新:它不会与OutOfMemoryError一起死亡。这是错误:

Error occurred during initialization of VM 
Could not allocate metaspace: 524288000 bytes 
+0

它耗尽内存并被'cgroups'杀死,或耗尽内存并抛出'OutOfMemoryError'? – EJP

+0

Java以'OutOfMemoryError'退出。我已经更新了这个问题。 – tombrown52

+0

所以它正在做你想做的。您只需找到适合您的“cgroups”限制的可行Java内存参数即可。如果没有一个,你不能这样做。 – EJP

回答

1

真的很难有效地ulimit java。许多池是无限的,当分配尝试失败时,JVM会发生灾难性故障。并非所有内存都是实际承诺的,但其中的大部分内容都保留下来,因此可以计入ulimit强加的虚拟内存限制。

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

这是JVM存储器的最公知的部分。这是大部分程序存储器的使用场合。它可以通过-Xmx-Xms选项进行控制。

元空间

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

压缩类空间

这似乎涉及到元空间。我无法确定这个类别是否会向操作系统释放内存,或者只能增长。默认最大值似乎为1g。它可以用'-XX:CompressedClassSpaceSize`选项来控制。

垃圾收集开销

似乎存在取决于所选择的垃圾收集器的开销的一个固定量,以及基于所述堆的大小的附加分配。观察表明,这种开销约为堆大小的5%。没有已知的限制选项(除了选择不同的GC算法)。

线程

每个线程储备1米它的堆栈。 JVM似乎预留了额外的内存作为防止堆栈溢出的安全措施。堆栈大小可以使用-Xss选项进行控制。安全大小无法控制。由于没有办法强制执行最大线程数,并且每个线程都需要一定量的内存,所以这个内存池在技术上是无界的。

Jar文件(和zip文件)

默认ZIP实现将使用zip文件存取存储器映射。这意味着每个访问的jar和zip文件都将被映射到内存中(需要预留的内存量等于文件大小的总和)。这种行为可以通过设置sun.zip.disableMemoryMapping系统属性(如在-Dsun.zip.disableMemoryMapping=true

NIO直接缓冲区

任何直接缓冲液(创建使用allocateDirect)将使用离堆存储器的量被禁用。最好的NIO性能来自直接缓冲区,所以很多框架都会使用它们。

JVM没有办法限制NIO缓冲区允许的内存总量,所以这个池在技术上是无界的。

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

如果您正在使用的任何本地库分配

本机内存,它们分配任何内存将是离堆。一些核心Java库(如java.util.zip.ZipFile)也使用消耗堆内存的本机库。

JVM没有办法限制本机库分配的内存总量,所以这个池在技术上是无界的。

的malloc舞台

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

设置MALLOC_ARENAS_MAX为1-4通常建议用于java,因为大多数频率分配是从堆中完成的,较低的竞技场计数将防止浪费的虚拟内存计数到ulimit。

这个类别在技术上并不是它自己的池,但它解释了额外内存的虚拟分配。