考虑什么构成了 Java 进程。
你有:
- JVM(一个 C 程序)
- JNI 数据
- Java字节码
- Java 数据
值得注意的是,它们都存在于 C 堆中(JVM 堆自然是 C 堆的一部分)。
在 Java 堆中只是 Java 字节码和 Java 数据。但是 Java 堆中还有“空闲空间”。
典型的(即 Sun)JVM 只在必要时增加它的 Java Heap,但从不缩小它。一旦它达到其定义的最大值 (-Xmx512M),它就会停止增长并处理剩下的任何东西。当最大堆用完时,您会收到 OutOfMemory 异常。
Xmx512M 选项不做的是限制进程的整体大小。它仅限制进程的 Java Heap 部分。
例如,您可能有一个人为的 Java 程序,它使用 10MB 的 Java 堆,但调用一个分配 500MB 的 C 堆的 JNI 调用。您可以看到您的进程大小如何,即使 Java 堆很小。此外,使用新的 NIO 库,您也可以将内存附加到堆外。
您必须考虑的另一个方面是 Java GC 通常是“复制收集器”。这意味着它从正在收集的内存中获取“实时”数据,并将其复制到内存的不同部分。复制到的这个空白空间不是堆的一部分,至少在 Xmx 参数方面不是。它就像“新堆”,在复制后成为堆的一部分(旧空间用于下一次 GC)。如果你有一个 512MB 的堆,它是 510MB,Java 将把实时数据复制到某个地方。天真的想法会是另一个大的开放空间(比如 500+MB)。如果你所有的数据都是“活的”,那么它需要一个像这样的大块来复制。
因此,您可以看到,在最极端的情况下,您需要至少双倍系统上的可用内存来处理特定的堆大小。512MB 堆至少需要 1GB。
事实证明,实际情况并非如此,内存分配等比这更复杂,但您确实需要大量空闲内存来处理堆副本,这会影响整个进程的大小。
最后,请注意 JVM 会做一些有趣的事情,例如将 rt.jar 类映射到 VM 以简化启动。它们映射在只读块中,并且可以在其他 Java 进程之间共享。这些共享页面将对所有 Java 进程“计数”,即使它实际上只消耗一次物理内存(虚拟内存的魔力)。
现在至于为什么您的进程继续增长,如果您从未遇到 Java OOM 消息,这意味着您的泄漏不在 Java 堆中,但这并不意味着它可能不在其他地方(JRE 运行时,a 3rd 方 JNI 库、本机 JDBC 驱动程序等)。