【为什么我的JVM能实际使用的内存比

作者:Nikita Salnikov-Tarnovski 译者:Amanda 校对:

“你好,你能过来看看帮我解决一个奇怪的问题么。”就是这个技术支持案例使我想起写下这篇帖子。眼前的这个问题就是关于不同工具对于可用内存大小检测的差异。

其实就是一个工程师在调查一个应用程序的过高的内存使用情况时发现,尽管该程序已经被指定分配2G堆内存,但是JVM检测工具似乎并不能确定进程实际能用多少内存。例如jconsole显示可用堆内存为1,963M,然而jvisualvm却显示能用2,048M。所以到底哪个工具才是对的,为什么检测结果会出现差异呢?

这确实是个挺奇怪的问题,特别是当最常出现的几种解释理由都被排除后,看来JVM并没有耍一些明显的小花招:

-Xmx和-Xms是相等的,,因此检测结果并不会因为堆内存增加而在运行时有所变化。通过关闭自适应调整策略(-XX:-UseAdaptiveSizePolicy),JVM已经事先被禁止动态调整内存池的大小。重现差异检测结果

要弄清楚这个问题的第一步就是要明白这些工具的实现原理。通过标准APIs,我们可以用以下简单语句得到可使用的内存信息。

System.out.println("Runtime.getRuntime().maxMemory()="+Runtime.getRuntime().maxMemory());

复制代码

而且确实,现有检测工具底层也是用这个语句来进行检测。要解决这个问题,首先我们需要一个可重复使用的测试用例。因此,我写了下面这段代码:

package eu.plumbr.test;//imports skipped for brevitypublic class HeapSizeDifferences {static Collection objects = new ArrayList();static long lastMaxMemory = 0;public static void main(String[] args) { try { List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); System.out.println("Running with: " + inputArguments); while (true) { printMaxMemory(); consumeSpace(); } } catch (OutOfMemoryError e) { freeSpace(); printMaxMemory(); }}static void printMaxMemory() { long currentMaxMemory = Runtime.getRuntime().maxMemory(); if (currentMaxMemory != lastMaxMemory) { lastMaxMemory = currentMaxMemory; System.out.format("Runtime.getRuntime().maxMemory(): %,dK.%n", currentMaxMemory / 1024); }}static void consumeSpace() { objects.add(new int[1_000_000]);}static void freeSpace() { objects.clear();}}

复制代码

这段代码通过将new int[1_000_000]

Running with: [-Xms2048M, -Xmx2048M]Runtime.getRuntime().maxMemory(): 2,010,112K.

复制代码

实际情况也确实如预估的那样,尽管我已经给JVM预先指定分配了2G对内存,在不知道为什么在运行期有85M内存不见了。你大可以把Runtime.getRuntime().maxMemory()的返回值2,010,112K 除以1024来转换成MB,那样你将得到1,963M,正好和2048M差85M。

找到根本原因

在成功重现了这个问题之后,我尝试用使用不同的GC算法,果然检测结果也不尽相同。

GC algorithmRuntime.getRuntime().maxMemory()-XX:+UseSerialGC2,027,264K-XX:+UseParallelGC2,010,112K-XX:+UseConcMarkSweepGC2,063,104K-XX:+UseG1GC2,097,152K

复制代码

除了G1算法刚好完整使用了我预指定分配的2G之外,其余每种GC算法似乎都不同程度地丢失了一些内存。

现在我们就该看看在JVM的源代码中有没有关于这个问题的解释了。我在CollectedHeap这个类的源代码中找到了如下的解释:

Running with: [-Xms2048M, -Xmx2048M]// Support for java.lang.Runtime.maxMemory():return the maximum amount of// memory that the vm could make available for storing ‘normal’ java objects.// This is based on the reserved address space, but should not include space// that the vm uses internally for bookkeeping or temporary storage// (e.g., in the case of the young gen, one of the survivor// spaces).virtual size_t max_capacity() const = 0;

复制代码

明白这一点之后问题就好解决了。打开并查看GC logging 信息之后我们发现,在Serial,Parallel以及CMS算法回收过程中丢失的那些内存,尺寸刚好等于JVM从2G堆内存中划分给Survivor区内存的尺寸。例如,在上面的ParallelGC算法运行时,GC logging信息如下:

理想的路总是为有信心的人预备着

【为什么我的JVM能实际使用的内存比

相关文章:

你感兴趣的文章:

标签云: