java的DirectBuffer源码分析(主要是DirectBuffer的资源分配和回收)
程序员文章站
2022-07-13 16:51:45
...
可以看到淘宝的一个说明:http://www.tbdata.org/archives/801
Java 2 SE 6 doc :
Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer’s content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system’s native I/O operations.
DirectBuffer通过免去中间交换的内存拷贝, 提升IO处理速度;
Java 2 SE 6 doc :
The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious.
DirectBuffer在-XX:MaxDirectMemorySize=xxM大小限制下[1], 使用Heap之外的内存, GC对此”无能为力”[2] ,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.
因此, 当系统应用场景满足:
大量原生类型数据使用
频繁IO操作
系统处理响应速度要求快且稳定
典型场景是网络数据传输, 可考虑合理应用DirectBuffer.
那么,DirectBuffer是怎么实现这些的呢:
allocateMemory 是一个本地方法,功能就是做一些参数检查和字节对齐,然后使用系统调用 malloc 申请一块内存。
从上面的分析也可以看出,创建一个 DirectByteBuffer 对象代价还是很大的,因为 malloc 系统调用代价就很大,需要 60 ~ 100 条 CPU 指令(一个 new Object() 只需要不足 10 条机器指令)。
注意,这个是操作系统直接分配的内存,java的垃圾回收是管不了的。
那是如何实现资源回收的呢。
cleaner 的类型是Cleaner,Cleaner是PhantomReference(俗称的幽灵引用)的子类。当整个对象被回收的时候,幽灵引用会被java的虚拟机加到一个队列里去,ReferenceQueue。Reference有一个静态内部类ReferenceHandler。
然后还有个静态代码块:
也就是说虚拟机已启动,ReferenceHandler 这个线程就启动了,那ReferenceHandler 会做什么呢
可以看到其中有:
就是会执行幽灵引用的clean方法。
在clean方法执行的其实是:
thunk是在构造方法传进去的。对于DirectByteBuffer来说,他的thunk是Deallocator:
Deallocator的run方法会做什么事情呢:
可以看到这个也是个native方法。这里就会把资源回收。
有些人问为什么不用finalize去做资源回收,搞个幽灵索引,搞怎么麻烦。主要是因为java的
finalize不是很安全。如果finalize再搞一个对象本身的强引用,那么这个对象就永远不会回收掉了。但是幽灵引用不一样,幽灵引用起作用在对象的finalize之后,也就是说这个对象这个时候已经回收了。这样会更安全。
Java 2 SE 6 doc :
Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer’s content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system’s native I/O operations.
DirectBuffer通过免去中间交换的内存拷贝, 提升IO处理速度;
Java 2 SE 6 doc :
The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious.
DirectBuffer在-XX:MaxDirectMemorySize=xxM大小限制下[1], 使用Heap之外的内存, GC对此”无能为力”[2] ,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.
因此, 当系统应用场景满足:
大量原生类型数据使用
频繁IO操作
系统处理响应速度要求快且稳定
典型场景是网络数据传输, 可考虑合理应用DirectBuffer.
那么,DirectBuffer是怎么实现这些的呢:
DirectByteBuffer(int paramInt) { super(-1, 0, paramInt, paramInt, false); Bits.reserveMemory(paramInt); int i = Bits.pageSize(); long l = 0L; try { l = unsafe.allocateMemory(paramInt + i); //这个是分配内存 } catch (OutOfMemoryError localOutOfMemoryError) { Bits.unreserveMemory(paramInt); throw localOutOfMemoryError; } unsafe.setMemory(l, paramInt + i, 0); if (l % i != 0L) this.address = (l + i - (l & i - 1)); else this.address = l; this.cleaner = Cleaner.create(this, new Deallocator(l, paramInt, null));//这个是负责回收资源。 this.viewedBuffer = null; }
public native long allocateMemory(long paramLong);
allocateMemory 是一个本地方法,功能就是做一些参数检查和字节对齐,然后使用系统调用 malloc 申请一块内存。
从上面的分析也可以看出,创建一个 DirectByteBuffer 对象代价还是很大的,因为 malloc 系统调用代价就很大,需要 60 ~ 100 条 CPU 指令(一个 new Object() 只需要不足 10 条机器指令)。
注意,这个是操作系统直接分配的内存,java的垃圾回收是管不了的。
那是如何实现资源回收的呢。
this.cleaner = Cleaner.create(this, new Deallocator(l, paramInt, null));
cleaner 的类型是Cleaner,Cleaner是PhantomReference(俗称的幽灵引用)的子类。当整个对象被回收的时候,幽灵引用会被java的虚拟机加到一个队列里去,ReferenceQueue。Reference有一个静态内部类ReferenceHandler。
private static class ReferenceHandler extends Thread
然后还有个静态代码块:
static { Object localObject1 = Thread.currentThread().getThreadGroup(); for (Object localObject2 = localObject1; localObject2 != null; localObject2 = ((ThreadGroup)localObject1).getParent()) localObject1 = localObject2; localObject2 = new ReferenceHandler((ThreadGroup)localObject1, "Reference Handler"); ((Thread)localObject2).setPriority(10); ((Thread)localObject2).setDaemon(true); ((Thread)localObject2).start(); }
也就是说虚拟机已启动,ReferenceHandler 这个线程就启动了,那ReferenceHandler 会做什么呢
public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } } }
可以看到其中有:
if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; }
就是会执行幽灵引用的clean方法。
在clean方法执行的其实是:
this.thunk.run();
thunk是在构造方法传进去的。对于DirectByteBuffer来说,他的thunk是Deallocator:
this.cleaner = Cleaner.create(this, new Deallocator(l, paramInt, null))
Deallocator的run方法会做什么事情呢:
public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(capacity); }
public native void freeMemory(long paramLong);
可以看到这个也是个native方法。这里就会把资源回收。
有些人问为什么不用finalize去做资源回收,搞个幽灵索引,搞怎么麻烦。主要是因为java的
finalize不是很安全。如果finalize再搞一个对象本身的强引用,那么这个对象就永远不会回收掉了。但是幽灵引用不一样,幽灵引用起作用在对象的finalize之后,也就是说这个对象这个时候已经回收了。这样会更安全。
下一篇: 百度站长工具 加广告 代码分析