java 申请内存_关于java直接内存

本文探讨了Java直接内存(DirectByteBuffer)的引入及其使用,通过代码调试跟踪Netty中ByteBuf的分配方式,展示了如何利用Unsafe进行直接内存的申请与释放。通过性能测试,比较了直接内存与堆内存的访问和申请速度,结果显示直接内存虽然申请速度较慢,但在访问效率上具有优势。直接内存避免了堆内存的二次拷贝,从而提升了I/O性能。
摘要由CSDN通过智能技术生成

java1.4引入了nio.DirectByteBuffer,可能大部分同学在工作中跟直接内存打交道的并不多,本篇将结合网上资料查阅和自己的理解来对java直接内存和非直接内存做一个细致的整理。如有不合理之处,欢迎评论区留言。

Netty服务端接收到消息体存储在ByteBuf中,接下来我们通过Debug的方式来跟踪一下ByteBuff的内存分配方式。

客户端发送的消息体在服务端AbstractNioByteChannel.read()中接收处理,在此处断点跟踪:

c1ca975d6a33

image.png

留意到PooledByteBufAllocator的属性:directByDefault为true;

进入byteBuf = allocHandle.allocate(allocator);

c1ca975d6a33

image.png

c1ca975d6a33

image.png

c1ca975d6a33

image.png

预执行表达式PlatformDependent.hasUnsafe()返回true,进入directBuffer内存申请;

继续往下看:

c1ca975d6a33

image.png

此处先做个最大申请内存的校验,最终我们跟踪到申请直接内存的相关代码:

c1ca975d6a33

image.png

从方法说明可以看到:直接内存申请后必须调用freeDirectNoCleaner来手动释放内存

PlatformDependent0中相关代码逻辑如下:

//调用sun.misc.Unsafe分配直接内存

static ByteBuffer allocateDirectNoCleaner(int capacity) {

return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);

}

//根据申请的内存地址和内存大小创建DerectByteBuff对象

static ByteBuffer newDirectBuffer(long address, int capacity) {

ObjectUtil.checkPositiveOrZero(capacity, "capacity");

try {

return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);

} catch (Throwable cause) {

// Not expected to ever throw!

if (cause instanceof Error) {

throw (Error) cause;

}

throw new Error(cause);

}

}

关于直接内存的分配我们用到了sum.misc.Unsafe,类中包含了大量的native方法。研究过jdk源码的同学会发现该类在很多jdk类中都有用到,比如AQS用到的Unsafe.park/Unsafe.unpark、原子操作compareAndSwapXXX、以及此处的allocateMemory/freeMemory等等

通过以上的代码跟踪我们看到Netty关于消息体的存储使用了直接内存的分配方式,这样做的优势在哪呢?

我们来做一个性能测试:

public class MemoryTest {

public static void heapAccess() {

long startTime = System.currentTimeMillis();

ByteBuffer buffer = ByteBuffer.allocate(1000);

for (int i = 0; i < 100000; i++) {

for (int j = 0; j < 200; j++) {

buffer.putInt(j);

}

buffer.flip();

for (int j = 0; j < 200; j++) {

buffer.getInt();

}

buffer.clear();

}

long endTime = System.currentTimeMillis();

System.out.println("堆内存访问:" + (endTime - startTime));

}

public static void directAccess() {

long startTime = System.currentTimeMillis();

ByteBuffer buffer = ByteBuffer.allocateDirect(1000);

for (int i = 0; i < 100000; i++) {

for (int j = 0; j < 200; j++) {

buffer.putInt(j);

}

buffer.flip();

for (int j = 0; j < 200; j++) {

buffer.getInt();

}

buffer.clear();

}

long endTime = System.currentTimeMillis();

System.out.println("直接内存访问:" + (endTime - startTime));

}

public static void heapAllocate() {

long startTime = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

ByteBuffer.allocate(100);

}

long endTime = System.currentTimeMillis();

System.out.println("堆内存申请:" + (endTime - startTime));

}

public static void directAllocate() {

long startTime = System.currentTimeMillis();

for (int i = 0; i < 100000; i++) {

ByteBuffer.allocateDirect(100);

}

long endTime = System.currentTimeMillis();

System.out.println("直接内存申请:" + (endTime - startTime));

}

public static void main(String args[]) {

for (int i = 0; i < 10; i ++) {

heapAccess();

directAccess();

}

System.out.println();

for (int i = 0; i < 10; i ++) {

heapAllocate();

directAllocate();

}

}

}

输出结果:

堆内存访问:62

直接内存访问:48

堆内存访问:39

直接内存访问:26

堆内存访问:61

直接内存访问:46

堆内存访问:98

直接内存访问:76

堆内存访问:59

直接内存访问:77

堆内存访问:47

直接内存访问:27

堆内存访问:40

直接内存访问:21

堆内存访问:55

直接内存访问:21

堆内存访问:50

直接内存访问:30

堆内存访问:50

直接内存访问:25

堆内存申请:12

直接内存申请:29

堆内存申请:40

直接内存申请:31

堆内存申请:21

直接内存申请:28

堆内存申请:1

直接内存申请:22

堆内存申请:6

直接内存申请:73

堆内存申请:1

直接内存申请:17

堆内存申请:1

直接内存申请:18

堆内存申请:1

直接内存申请:373

堆内存申请:2

直接内存申请:38

堆内存申请:3

直接内存申请:35

可以看出直接内存申请较慢,但访问效率高。

为什么呢???

回顾一下java内存模型:

c1ca975d6a33

image.png

我加上内核空间和用户空间的概念

我的理解是堆内存的申请是直接从已分配的堆空间中取一块出来使用,不经过内存申请系统调用,而直接内存的申请则需要本地方法通过系统调用完成。

笔者尝试用strace命令去跟综了下ByteBuffer.allocateDirect和ByteBuffer.allocate涉及的系统调用,然而并没有发现不一样的地方,感兴趣的朋友欢迎评论区交流。

而关于直接内存和非直接内存的访问效率,我们看看ByteBuffer上的一段官方说明:

c1ca975d6a33

image.png

在java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。

转载请备注原文链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值