1 定义

JVM内存结构图

直接内存并不属于JVM的内存结构,它是物理机的内存,但是JVM虚拟机可以调用该部分内存。

直接内存的使用:

  • 常见于NIO,用于数据缓冲区
  • 分配回收的代价较高,但是速度很快
  • 不收JVM内存回收管理

2 正常IO读取

正常IO读取结构图

从上图的结构图中,我们可以看到,当java程序需要读取文件时,首先会在java堆内存中new一个缓冲区,然后系统内存从磁盘中读取文件,再然后在将系统缓冲区中的字节流复制到java堆内存的缓冲区中,然后在由java程序调用。

这样做有一个缺点,就是需要开启两块内存,效率会很低。

3 直接内存读取

直接内存结构图

从图中可以看到,当java程序使用直接内存时,首先java程序在系统内存中分配一块直接内存块,这一内存块是系统内存和java堆内存可以共享的,那么系统内存读取到的磁盘文件就可以直接由java堆内存使用,这样就省去了复制的操作,大大节约了时间开销。

4 直接内存分配内存

通过java中的unsafe对象分配一块直接内存,直接内存大小在分配时指定。直接内存由于不受JVM的管理,所以直接内存的释放,必须主动调用unsafe对象进行释放,才能将直接内存释放。

这是ByteBuffer的分配直接内存的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DirectByteBuffer(int cap) {                   // package-private

super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);

long base = 0;
try {
base = unsafe.allocateMemory(size); //分配直接内存
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
/*
cleaner对象用来释放直接内存,cleaner对象关联了当前的ByteBuffer对象,因为ByteBuffer对象是受java虚拟机管理的,直接内存不受java虚拟机管理,所以这里的关联,就是为了在当ByteBuffer被释放的时候,直接内存也被释放,只不过是被unsafe对象释放的,并不是Java虚拟机释放的。
*/
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//释放直接内存
att = null;
}

cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//释放直接内存
解释:这里使用cleaner对象用来释放直接内存,cleaner对象关联了当前的ByteBuffer对象,因为ByteBuffer对象是受java虚拟机管理的,直接内存不受java虚拟机管理,所以这里的关联,就是为了在当ByteBuffer被释放的时候,直接内存也被释放,只不过是被unsafe对象释放的,并不是Java虚拟机释放的。

cleaner对象是一个虚引用对象。

Deallocator类源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static class Deallocator
implements Runnable
{

private static Unsafe unsafe = Unsafe.getUnsafe();

private long address;
private long size;
private int capacity;

private Deallocator(long address, long size, int capacity) {
assert (address != 0);
this.address = address;
this.size = size;
this.capacity = capacity;
}

public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);//主动释放直接内存
address = 0;
Bits.unreserveMemory(size, capacity);
}

}

5 直接内存回收

从第4节我们可以看到,直接内存的会随着ByteBuffer对象的被回收,然后触发cleaner对象,调用Unsafe对象将直接内存回收,看起来也像是一种自动回收的方法。

但是,由于ByteBuffer对象的回收,是遵循JVM回收机制的,也就是说,得达到一定的回收条件才会回收ByteBuffer对象。那么直接内存也不会被回收,这样就会导致内存不足。所以建议使用手动调用Unsafe的方法释放直接内存。

写在最后

欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。
微信公众号