直接内存(Direct Memory)是在Java中使用较为底层的内存管理方式,它通过Java的NIO包中的ByteBuffer类
来实现,让Java能够在堆外分配内存。这种方式通常用于需要大量、频繁的IO操作的场景,因为它可以减少在Java堆和本地堆之间复制数据的次数,从而提高性能。以下是关于直接内存的一些分析和代码示例。
1、不使用直接内存
io读取数据流程:会先把磁盘文件读到系统内存的缓存区,再把系统缓冲区中的数据读到Java缓冲区,然后才能写入到java 字节代码中。
2、使用直接内存
nio读取数据流程(调用直接内存方法):会先把磁盘文件读到直接内存,系统和java代码都可以访问这块内存区域,大大减少了io时间。
3、直接内存的特点
- 常见于 NIO 操作时,用于数据缓冲区,提高IO性能
- 分配回收成本较高,但读写性能高
- 不受JVM 内存回收管理(系统分配的内存,不受JVM管理)
4、代码演示
在Java中使用直接内存需要通过ByteBuffer.allocateDirect()方法进行内存分配。以下是一个简单的代码示例:
import java.nio.ByteBuffer;
public class DirectMemoryTest {
public static void main(String[] args) {
// 分配直接内存
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
// 使用直接内存
directBuffer.put((byte) 123); // 示例:写入数据
directBuffer.flip(); // 转换为读模式
byte value = directBuffer.get(); // 示例:读取数据
System.out.println("读取的数据为: " + value);
// 提示JVM进行垃圾收集,注意:这不会立即回收直接内存,直到 directBuffer 不可达后才会开始回收直接内存
System.gc();
}
}
5、NIO缓冲区分析
NIO的缓冲区是一块可以写入数据,然后可以从中读取数据的内存区域。Buffer是一个抽象类,它最常用的类型是ByteBuffer。除了ByteBuffer,还有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer等。
当通过 allocateDirect() 方法创建直接缓冲区时,Java会在操作系统的本地堆中分配内存。由于避开了JVM堆,因此提高了效率,尤其是在大量数据读写的情况下。但是需要注意,直接缓冲区的创建和销毁的成本较高,不适用于生命周期短暂的情况。
6、直接内存垃圾回收
在Java中,可以通过调用ByteBuffer的cleaner() 方法来获取一个 Cleaner 对象,然后通过它来手动释放直接内存。示例如下:
import sun.misc.Cleaner;
import java.nio.ByteBuffer;
public class DirectMemoryExample {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); // 分配直接内存
// 使用直接内存
Cleaner cleaner = Cleaner.create(byteBuffer, new Runnable() {
@Override
public void run() {
// 在此处执行直接内存释放操作
// 例如: Unsafe.freeMemory(memoryAddress);
}
});
// 当ByteBuffer对象变为不可达时,Cleaner会自动执行释放操作
}
}
在这个示例中,当 ByteBuffer 对象不再被引用时,Cleaner 对象会调用指定的 Runnable 来执行直接内存的释放操作。这种方式虽然不是由Java虚拟机的垃圾回收器来管理直接内存,但是可以帮助确保直接内存得到及时释放,避免内存泄漏。
7、注意事项
使用直接内存需要注意几个关键点:
- 合理大小:分配太多直接内存可能会导致系统级别的内存不足。
- 释放资源:使用完毕后应及时释放直接内存,虽然Java会最终释放,但不及时释放可能会造成内存泄露。
- 内存映射文件:直接内存通常与内存映射文件(Memory-Mapped File)配合使用,适用于有大量数据读写磁盘的场景。
通过正确使用直接内存,可以在某些场景下显著提升Java应用程序的性能。然而,也要谨慎管理,避免内存泄漏和其他资源管理错误。