文章目录

1.定义

不是虚拟机的内存,是系统内存。​​Direct Memory​

  • 常见于NIO操作时,用于数据缓存区
  • 分配回收成本过高,但读写性能高
  • 不受JVM内存回收管理

2.基本使用

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
* 演示 ByteBuffer 作用
*/
public class Demo1_9 {
static final String FROM = "E:\\b.mp4";
static final String TO = "E:\\a.mp4";
static final int _1Mb = 1024 * 1024;

public static void main(String[] args) {
io(); // io 用时:1535.586957 1766.963399 1359.240226
directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
}

private static void directBuffer() {
long start = System.nanoTime();
try (FileChannel from = new FileInputStream(FROM).getChannel();
FileChannel to = new FileOutputStream(TO).getChannel();
) {
ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
while (true) {
int len = from.read(bb);
if (len == -1) {
break;
}
bb.flip();
to.write(bb);
bb.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
}

private static void io() {
long start = System.nanoTime();
try (FileInputStream from = new FileInputStream(FROM);
FileOutputStream to = new FileOutputStream(TO);
) {
byte[] buf = new byte[_1Mb];
while (true) {
int len = from.read(buf);
if (len == -1) {
break;
}
to.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("io 用时:" + (end - start) / 1000_000.0);
}
}

2.1.Java操作磁盘文件

JVM专题(九)-直接内存_性能优化
当​​​java​​​读取磁盘文件时,会从用户态切换到内核态,才能去操作系统内存。读取时,系统内存先开辟一块缓存空间,磁盘文件分块读取。然后java虚拟机内存再开辟缓存空间​​new Byte[]​​来读取系统内存的文件。由于有从系统内存读取到java虚拟机的内存,所以效率较低。

2.2.NIO操作磁盘文件

JVM专题(九)-直接内存_spring_02
读取磁盘文件时,会有一块直接内存,​​​Java​​虚拟机和视同内存都能访问使用,所以效率更高。

2.3.内存溢出

/**
* 演示直接内存溢出
*/
public class Demo1_10 {
static int _100Mb = 1024 * 1024 * 100;

public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
} finally {
System.out.println(i);
}
// 方法区是jvm规范, jdk6 中对方法区的实现称为永久代
// jdk8 对方法区的实现称为元空间
}
}

JVM专题(九)-直接内存_优化_03

2.4.分配和释放原理

  • 使用了​​Unsafe​​​对象完成直接内存的分配回收,并且回收需要主动调用​​freeMemory​​方法。
  • ​ByteBuffer​​​的实现类内部,使用了​​Cleaner​​​(虚引用)来检测​​ByteBuffer​​​对象,一旦​​ByteBuffer​​​对象被垃圾回收,那么就会由​​ReferenceHandler​​​线程通过Cleaner的clean方法调用​​freeMemory​​来释放直接内存。
public class Demo1_26 {
static int _1Gb = 1024 * 1024 * 1024;

/*
* -XX:+DisableExplicitGC 显式的
*/
public static void main(String[] args) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
System.out.println("分配完毕...");
System.in.read();
System.out.println("开始释放...");
byteBuffer = null;
System.gc(); // 显式的垃圾回收,Full GC
System.in.read();
}
}
  • 直接内存不受jvm内存管理,但是这段代码示例中,当​​byteBuffer​​​ = null,执行垃圾回收后,直接内存却被释放了。这是因为跟​​jdk​​​中的一个类​​Unsafe​​有关。
  • ​-XX:+DisableExplicitGC​​ 禁用显式的垃圾回收。只有等到jvm自己进行垃圾回收才会回收。
/**
* 直接内存分配的底层原理:Unsafe
*/
public class Demo1_27 {
static int _1Gb = 1024 * 1024 * 1024;

public static void main(String[] args) throws IOException {
Unsafe unsafe = getUnsafe();
// 分配内存
long base = unsafe.allocateMemory(_1Gb);
unsafe.setMemory(base, _1Gb, (byte) 0);
System.in.read();

// 释放内存
unsafe.freeMemory(base);
System.in.read();
}

public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

​Unsafe​​​类调用其方法​​freeMemory​​来释放了直接内存。

ByteBuffer源码
JVM专题(九)-直接内存_虚拟机_04
​​​ByteBuffer​​​分配直接内存是通过​​new​​​ 了一个​​DirectByteBuffer​​​JVM专题(九)-直接内存_虚拟机_05
在​​DirectByteBuffer​​内部可以看到,确实是用​​Unsafe​​类去分配内存。

这里调用了一个​​Cleaner​​​的​​create​​​方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是​​DirectByteBuffer​​​)被回收以后,就会调用​​Cleaner​​​的​​clean​​​方法,来清除直接内存中占用的内存
JVM专题(九)-直接内存_jvm_06
JVM专题(九)-直接内存_性能优化_07
​​​DirectByteBuffer​​​的​​run​​方法释放了直接内存。

3.直接内存的回收机制总结

使用了 ​​Unsafe​​​ 对象完成直接内存的分配回收,并且回收需要主动调用 ​​freeMemory​​ 方法

​ByteBuffer​​​ 的实现类内部,使用了 ​​Cleaner​​​ (虚引用)来监测 ​​ByteBuffer​​​ 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ​​ReferenceHandler​​​ 线程通过 ​​Cleaner​​​ 的 ​​clean​​​ 方法调 用 ​​freeMemory​​ 来释放直接内存