无题

java直接内存分配的原理分析

1. java的内存布局

众所周知,java中对象的创建都是基于java堆进行的,如下为java的内存结构

2. java对象内存布局

java对象在堆内存中的内存布局如下所示,分别显示了java 的对象和数组对象的内存结构

对于java对象实例,其都是存储在java堆中的,接下来我们说明这个问题,对于直接操作内存,我们需要获取Unsafe对象

1
2
3
4
5
6
7
8
9
10
11
private static Unsafe unsafe;

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

引入jol依赖,利用jol获取对象实例地址,由于关闭了类指针压缩,所以字节数组的数据部分相对实例地址的位置偏移了24字节,通过unsafe对象直接获取该内存中的值,即可获取堆内存中数组array的各个值,答应如下,实现对内存的直接操作

1
2
3
4
5
6
byte[] array = new byte[]{66, 77, 88, 99};
long l = VM.current().addressOf(array);
long address = l + 24;
for (int i = 0; i < 4; i++) {
System.out.println(i + " : " + unsafe.getByte(address + i));
}

输出结果:

0 : 66
1 : 77
2 : 88
3 : 99

上面展示的案例安全吗?必定是不安全的,因为对java堆了解的人都知道,堆区的对象为了垃圾回收是在一直移动的,所以直接对内存的十分危险,下面举例说明

1
2
3
4
5
6
byte[] array = new byte[]{66, 77, 88, 99};
long l1 = VM.current().addressOf(array);
System.out.println("垃圾回收前数组array地址 : " + Long.toHexString(l1));
System.gc();
long l2 = VM.current().addressOf(array);
System.out.println("垃圾回收后数组array地址 : " + Long.toHexString(l2));

得出的结果如下:

垃圾回收前数组array地址 : 711e71998
垃圾回收后数组array地址 : 703a07828

可以看出回收前和回收后的地址发生了变化,所以在堆区对内存的操作十分危险,可能会影响到其他堆区的对象

3. java直接内存

java中有直接对内存操作的对象DirectByteBuffer,何为直接内存,就是脱离堆区的控制,防止垃圾回收对地址产生影响,可以理解为其就是c和c++中堆内存的分配

在Java中,底层都是通过 Unsafe#allocateMemory 分配直接内存,所以直接看该函数的原理即可

经过一系列调用,最终会调用native方法 Unsafe#allocateMemory0,通过阅读该部分的 c++源码,如下

1
2
3
4
5
6
UNSAFE_LEAF(jlong, Unsafe_AllocateMemory0(JNIEnv *env, jobject unsafe, jlong size)) {
size_t sz = (size_t)size;
assert(is_aligned(sz, HeapWordSize), "sz not aligned");
void* x = os::malloc(sz, mtOther);
return addr_to_java(x);
} UNSAFE_END

可以看到底层也是调用了操作系统的malloc函数进行内存的申请,而操作系统不同对于malloc的实现也不同,下面简单介绍一下linux下的底层实现

malloc函数在linux系统中底层在是通过brk()mmap() 来实现内存的分配,当分配的内存大于128K时,调用**mmap()函数在Memory Mapping Region内存映射器内进行内存分配,创建匿名的虚拟文件的内存映射;否则调用brk()**函数在Heap堆区进行内存分配