前言
区别于 JVM 虚拟机提供的堆内内存,堆外内存不是由 JVM 管理,而是操作系统直接管理,分配内存空间于操作系统的用户态、JVM 虚拟机堆外的内存区域。
为什么需要堆外内存?
就像你做开发一样,总有标准接口无法解决的场景,需要一些个性化的方式进行处理。这里,堆外内存也是一样的道理。
堆外内存的一个核心目的是脱离JVM的内存管理,以便更灵活地支配内存空间。
堆外内存
堆外内存的优势
脱离JVM垃圾回收的管理:
堆外内存不受 JVM 垃圾回收机制的管理,因此可以避免垃圾回收带来的停顿和性能波动。这对于需要稳定性能的应用程序(如高频交易系统)非常重要。
比如 Netty,设计的核心目标之一是提升性能和减少垃圾回收对应用程序的影响。因此 Netty 会在部分场景下使用 直接内存 (Direct Memory),即堆外内存,从而降低了垃圾回收的频率和停顿时间。
直接内存:
- Netty 使用 ByteBuf 作为其数据缓冲区的抽象。ByteBuf 可以使用堆内存(Heap Memory)或直接内存(Direct Memory)。
- 直接内存是在 JVM 堆之外分配的内存,减少了堆内存的使用,从而降低了垃圾回收的频率和停顿时间。
- 使用直接内存可以提高 I/O 操作的性能,因为它避免了从堆内存到操作系统内核缓冲区的复制。
灵活的内存管理:
开发者可以更灵活地管理内存的分配和释放,而不必依赖于JVM的垃圾回收。这对于需要精细控制内存使用的应用程序(如缓存系统)非常有用。
减少堆内存压力:
使用堆外内存可以减少Java堆的使用,从而降低垃圾回收的频率和停顿时间。这对于需要处理大量数据的应用程序(如大数据处理、流媒体服务器等)非常有利。
提高I/O操作效率:
堆外内存可以直接与操作系统的I/O操作交互,减少Java堆与内核缓冲区之间的中间复制步骤,从而提高I/O操作的效率。
为什么减少了复制操作?
1、传统Java堆内存的I/O操作:
通常情况下,Java堆内存中的数据需要先复制到一个中间缓冲区(通常是内核缓冲区)才能进行I/O操作。这涉及从 Java堆 到 内核缓冲区 的复制。
2、直接缓冲区的I/O操作:
使用直接缓冲区时,数据可以直接从用户态的直接缓冲区传输到内核缓冲区。这减少了从Java堆到内核缓冲区的中间复制步骤。
直接缓冲区允许应用程序在用户态内存中准备数据,然后直接传输到内核态进行I/O操作。这种方式减少了 Java堆 与 内核缓冲区之间的复制。
操作系统也可以通过 DMA(直接内存访问)等技术进一步优化数据传输,减少 CPU 的参与。
堆外内存的缺点
复杂的内存管理:
使用堆外内存需要开发者手动管理内存的分配和释放,增加了内存泄漏的风险。
需要额外的代码来处理内存管理,增加了开发和维护的复杂性。 对编码者的要求更高
可能的性能开销:
虽然堆外内存可以提高 I/O 性能,但在某些情况下,访问堆外内存可能比访问堆内存更慢,因为它需要通过 JNI(Java Native Interface)进行访问。
调试困难:
内存泄漏和其他内存管理问题可能更难以调试,因为它们不在 JVM 的直接管理之下。
使用场景
大数据处理:
在处理大数据时,使用堆外内存可以避免频繁的垃圾回收,提高处理效率。
网络通信:
在高性能网络应用中,使用直接缓冲区可以提高数据传输效率。比如 Netty,利用直接内存,比如数据从 堆内 与用户态之间的相互拷贝,从而大大提升处理效率。
缓存系统:
一些缓存系统(如Apache Ignite)利用堆外内存来存储大量数据,以避免垃圾回收的影响。可以更加合理的控制缓存过期时间、缓存清理时间等。
小结
1、堆外内存是 JVM 堆外分配的内存空间,由操作系统管理,而不是 JVM
2、堆外内存空间也是属于操作系统的用户态空间
3、堆外内存可以使用更大、更加灵活的内存空间,提升 IO 数据传输效率;同时,对 GC 频次也可以更灵活的掌控。
4、堆外内存使用通常需要更加小心,因为它脱离了 JVM 管控,容易导致内存泄漏等无法及时清理。
虽然堆外内存本身仍然是用户态内存,但它通过减少Java堆与内核缓冲区之间的复制步骤,提高了I/O操作的效率。这种优化特别适用于高性能网络应用和需要频繁I/O操作的场景。
总的来说,堆外内存在需要高性能和低延迟的应用场景中非常有用,但也需要谨慎管理,以避免潜在的问题。