前言
在 Java 程序运行过程中,操作系统为其分配了物理内存和虚拟内存。理解这两者的概念有助于明晰内存管理和性能优化。
一、物理内存
物理内存是指计算机的实际 RAM(随机存取存储器)。Java 进程在运行时需要向操作系统请求内存资源,操作系统通过分配物理内存来满足 Java 进程的内存需求。
简而言之,物理内存是计算机系统的一种存储介质和磁盘不同、容量相对较小,通常在几GB到几十GB之间,常常用于临时数据存储。JVM 在堆内存、栈内存等区域中分配的内存实际上会映射到物理内存。
二、虚拟内存
虚拟内存是操作系统的一种内存管理机制,它允许应用程序看到的内存地址空间远大于物理内存。虚拟内存结合了 RAM 和磁盘(通常是交换空间 swap)的存储资源,将其作为一个大的统一内存空间对外暴露。
JVM 和应用程序看到的是虚拟地址空间,而非直接访问物理内存。
虚拟内存通过分页(paging)机制管理,将物理内存和磁盘上的页面文件结合使用。当物理内存不足时,操作系统会将不常用的内存页面换出到磁盘,并在需要时从磁盘换入。
简而言之,虚拟内存会指向物理内存或者磁盘,当JVM处理的数据处于磁盘上时,JVM会将磁盘中的数据load进入物理内存。物理内存不足时,将内存中不需要处理的数据放入磁盘中。
三、Java内存模型
- 堆(Heap):这是 Java 应用程序使用的主要内存区域,用于存放对象。堆的大小可以通过 JVM 参数 -Xms 和 -Xmx 来设置。堆内存被操作系统映射到虚拟地址空间,具体的内存页会在运行时根据需要分配到物理内存。
- 非堆内存(Non-Heap Memory):包括元空间(Metaspace)、代码缓存、直接内存等区域。它们用于存储类信息、常量、JIT 编译的代码等。这部分内存也消耗虚拟内存。
- 栈(Stack):每个线程都有自己的栈内存,用于存储局部变量和方法调用栈帧。栈的大小可以通过 JVM 参数 -Xss 来设置。栈也是分配在虚拟内存中的。
- 直接内存(Direct Memory):这是由 NIO(New I/O)库使用的内存区域,允许 Java 直接从堆外分配内存,用于更高效地进行 I/O 操作。直接内存不是分配在堆上,但消耗的是虚拟内存,大小可以通过 -XX:MaxDirectMemorySize 来配置。
四、总结与反思
物理内存和虚拟内存的区别在于前者是实际的内存资源,后者是操作系统通过映射创建的逻辑空间。
五、附录
1. 有虚拟内存为什么Java进程还会OOM?
堆内存不足
Java 应用程序的堆内存是由 JVM 管理的,虚拟内存并不意味着可以无限制地分配堆内存。如果应用程序请求的堆内存超过了 JVM 设置的最大堆大小(通过 -Xmx 配置),即使系统有足够的虚拟内存,JVM 也会抛出 OOM。
元空间不足
从 Java 8 开始,元空间用于存储类的元数据,元空间大小不是固定的。如果动态加载了大量类而未配置足够的元空间,可能会导致 OOM。这与虚拟内存的可用性无关。
直接内存不足
直接内存(通过 NIO 使用)是堆外分配的内存。如果应用程序需要大量直接内存并且未配置足够的最大直接内存(-XX:MaxDirectMemorySize),也会引发 OOM。
总结
虚拟内存虽然提供了扩展的地址空间,但 Java 进程的内存管理依然受到堆、元空间和直接内存等限制的影响。
简而言之,虚拟内存解决了计算过程将所有数据load到内存的困境,但是仍然受限于物理内存。
举个例子:写一个程序计算 a+b+c, 虚拟内存可以load a+b,然后再将计算结果x + c,保证内存中不会将不需要此刻处理的数据load到内存。
但是,对于JVM运行过程中正在使用的数据,仍然需要load到物理内存中。