理解JVM内存分配和Java内存溢出:堆空间

28

我正在深入研究JVM中内存分配的工作原理。

在我的应用程序中,我遇到了内存不足:堆空间异常。

我知道可以传递VM参数,如Xms和Xmx,以增加JVM为运行进程分配的堆空间。这是解决问题的一种可能方案,或者我可以检查代码中的内存泄漏并在那里修复问题。

我的问题是:

1)JVM实际上如何为自身分配内存?这与操作系统如何将可用内存通信给JVM有什么关系?或者更一般地说,任何进程的内存分配实际上是如何工作的?

2)虚拟内存如何发挥作用?假设您有一个具有32GB物理内存的系统,并将所有32GB都分配给Java进程。假设您的进程实际上消耗了所有32GB的内存,我们如何强制该过程使用虚拟内存而不是遇到OOM异常?

谢谢。


4
如果你还没有使用过像VisualVM这样的分析器,请使用一下。它将向你展示应用程序内部发生了什么,例如哪些对象没有被垃圾回收以及何时分配了内存,为你提供丰富的见解。 - Kyle
4个回答

28
JVM如何为自己分配内存?对于堆来说,它会分配一个最大尺寸的连续内存块。刚开始时,这是虚拟内存,但随着时间的推移,操作系统控制下被使用的部分就变成了真正的内存。
这与操作系统如何将可用内存传达给JVM有什么关系?JVM不知道OS中的空闲内存。
或者更一般地说,任何进程的内存分配实际上是如何工作的?通常使用malloc和free。
虚拟内存起到了什么作用?首先分配虚拟内存,然后按需转换为实际内存。这对于任何进程来说都很正常。
假设您有一个具有32GB物理内存的系统,并将所有32GB分配给Java进程。这是不可能的。操作系统需要一些内存,并且会有其他用途的内存。即使在JVM内部,堆也只是所用内存的一部分。如果您有32 GB的内存,则建议将最大堆设置为24 GB。
假设您的进程实际上消耗了全部32GB内存,如何强制该进程使用虚拟内存而不是遇到OOM异常?应用程序从一开始就使用虚拟内存。您不能使堆过大,因为如果开始交换,您的机器(而不仅仅是应用程序)将变得无法使用。您可以谨慎地使用非堆内存来使用超过物理内存的更多内存。但是,托管内存必须在物理内存中,因此如果需要32 GB的堆,则需购买64 GB的物理内存。

问题:您能告诉我为什么JVM_outOfMemory是错误而不是异常吗? - Tushar Pandey
6
从Javadoc 8的Error中翻译:Error是Throwable的一个子类,表示合理应用程序不应尝试捕获的严重问题。大多数这样的错误都是异常情况。你不希望catch(Exception e)捕获这样的错误。 - Peter Lawrey
在32GB物理内存中,你可以指定JVM使用32GB,因为它最初是虚拟内存,对吗?也就是说,Java程序仍然会启动?只有当它实际尝试使用那么多内存时才会抛出错误,是这样吗? - ealeon
@ealeon 它预先保留内存,因此它取决于操作系统是否允许这样做。 - Peter Lawrey
@PeterLawrey 我被告知情况并非如此。https://stackoverflow.com/questions/54318518/how-much-physical-memory-jvm-allocate-for-its-heap?noredirect=1#comment95461255_54318518 - ealeon
Linux/Unix/MacOS会按需懒惰地分配内存,除非您使用JVM命令行选项来预分配内存。您可以创建一个大小超过主内存+交换空间的JVM。在Windows上,它会急切地分配内存,并防止进程创建无法立即提供内存/交换空间的JVM。 - Peter Lawrey

8

JVM(或任何进程)想要分配内存,将会调用 C 运行时的 'malloc' 函数。此函数维护 C 运行时的堆内存。它从操作系统内核中获取内存,用于此目的的函数与平台有关;在 Linux 中,可能使用 brk 或 sbrk 系统调用。

一旦 JVM 获得了内存,它自己管理内存,将其分配给运行程序创建的各个对象。

虚拟内存完全由操作系统内核处理。内核管理将物理内存页面映射到各个进程的地址空间;如果系统中所有进程需要的物理内存比可用内存少,则操作系统内核将一些内存交换到磁盘上。

您不能(也不需要)强制进程使用虚拟内存。对于进程而言,它是透明的。

如果出现 'out of memory' 错误,则原因可能是:

  1. JVM的限制已经被超出。这些由各种命令行参数和/或属性控制,正如您在问题中所述。

  2. 操作系统可能已经用尽了交换空间(或者根本没有配置任何交换空间)。或者一些操作系统甚至不支持虚拟内存,此时你已经用完了真实内存。

  3. 大多数操作系统都有管理员限制进程消耗内存的工具 - 例如,在Linux中setrlimit系统调用和/或ulimit shell命令,两者都设置内核将遵守的限制。如果进程请求的内存超过了限制,则会失败(通常会导致内存不足消息)。


2
  1. JVM从操作系统中分配Java堆内存,然后为Java应用程序管理堆。当应用程序创建一个新对象时,JVM会子分配一段连续的堆内存来存储它。堆中由任何其他对象引用的对象是“活动”的,并且只要它继续被引用,就会一直留在堆中。不再被引用的对象是垃圾,可以清除出堆以回收它们占用的空间。JVM执行垃圾回收(GC)以删除这些对象,并重新组织留在堆中的对象。
    来源:http://pubs.vmware.com/vfabric52/index.jsp?topic=/com.vmware.vfabric.em4j.1.2/em4j/conf-heap-management.html

  2. 在使用虚拟内存的系统中,物理内存被分成相等大小的页面。进程所寻址的内存也被分成相同大小的逻辑页面。当进程引用一个内存地址时,内存管理器从磁盘中获取包含所引用地址的页面,并将其放置在RAM中的一个空闲物理页面中。

来源:http://searchstorage.techtarget.com/definition/virtual-memory

2

1
很不幸,这个链接现在不可用了 :( - Jonatas Emidio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接