这是我正在使用的命令和我正在设置的值的示例:
ulimit -Sv 1500000 java \ -Xmx1000m -Xms1000m \ -XX:MaxMetaspaceSize=500m \ -XX:CompressedClassSpaceSize=500m \ -XX:+ExitOnOutOfMemoryError \ MyClass
从理论上讲,我已经考虑了所有可以找到文档的内容.堆(1000m)和元空间(500m).但是在初始化JVM时它仍然会耗尽内存.直到我将ulimit设置为比堆元空间大600mib时才会发生这种情况.
我错过了什么类型的记忆,以便我可以适当地设置ulimit?
使用案例:我在内存有限的Docker容器中运行任务.这意味着linux cgroups正在进行限制.当超出内存限制时,cgroup只能暂停或终止超出其范围的进程.我真的希望java进程在出现问题时优雅地失败并且它使用太多内存以便包装bash脚本可以向任务启动器报告错误.
我们使用java 8所以我们需要担心Metaspace而不是permgen.
更新:它不会因OutOfMemoryError而死亡.这是错误:
Error occurred during initialization of VM Could not allocate Metaspace: 524288000 bytes
解决方法
经过大量调查,我发现了许多不同类型的内存java使用.此答案适用于64位系统上的OpenJDK和Oracle 8.x:
堆
这是JVM内存中最容易理解的部分.它是使用大部分程序存储器的地方.它可以使用-Xmx和-Xms选项进行控制.
元空间
这似乎包含有关已加载的类的元数据.我无法确定这个类别是否会向操作系统释放内存,或者它是否只会增长.默认最大值似乎为1g.它可以使用-XX:MaxMetaspaceSize选项进行控制.注意:如果不指定Compressed类空间,指定它可能不会执行任何操作.
压缩类空间
这似乎与Metaspace有关.我无法确定这个类别是否会向操作系统释放内存,或者它是否只会增长.默认最大值似乎为1g.它可以使用’-XX:CompressedClassSpaceSize`选项进行控制.
垃圾收集器开销
根据所选的垃圾收集器,似乎存在固定的开销量,以及基于堆大小的额外分配.观察表明,此开销约为堆大小的5%.没有已知的限制此选项(除了选择不同的GC算法).
每个线程为其堆栈预留1米. JVM似乎预留了额外的50米内存作为防止堆栈溢出的安全措施.可以使用-Xss选项控制堆栈大小.安全尺寸无法控制.由于无法强制执行最大线程数,并且每个线程都需要一定量的内存,因此这个内存池在技术上是无限制的.
默认的zip实现将使用内存映射来进行zip文件访问.这意味着访问的每个jar和zip文件都将被内存映射(需要一定量的保留内存等于文件大小的总和).可以通过设置sun.zip.disableMemoryMapping系统属性来禁用此行为(如-Dsun.zip.disableMemoryMapping = true)
NIO Direct Buffers
任何直接缓冲区(使用allocateDirect创建)都将使用该堆的堆外内存.最好的NIO性能带有直接缓冲区,因此很多框架都会使用它们.
JVM无法限制NIO缓冲区允许的总内存量,因此该池在技术上是无限制的.
此外,对于接触缓冲区的每个线程,此内存在堆上重复.有关详细信息,请参见this.
库分配的本机内存
如果您使用的是任何本机库,则它们分配的任何内存都将在堆外.一些核心java库(如java.util.zip.ZipFile)也使用消耗堆内存的本机库.
JVM无法限制本机库分配的内存总量,因此该池在技术上是无限制的.
malloc竞技场
JVM对许多这些本机内存请求使用malloc.为避免线程争用问题,malloc函数使用多个预分配池.默认池数等于8 x cpu,但可以通过设置环境变量MALLOC_ARENAS_MAX来覆盖它.即使不是全部使用,每个池也会保留一定量的内存.
对于java,通常建议将MALLOC_ARENAS_MAX设置为1-4,因为最常见的分配是从堆完成的,较低的竞技场计数将防止浪费的虚拟内存向ulimit计数.
此类别在技术上不是它自己的池,但它解释了额外内存的虚拟分配.