原标题:JDK 源码阅读 : DirectByteBuffer
源点:木杉的博客 ,
style="font-size: 1陆px;">imushan.com/2018/08/29/java/language/JDK源码阅读-DirectByteBuffer/
在文章JDK源码阅读-ByteBuffer中,大家学习了ByteBuffer的准备。不过他是三个抽象类,真正的实现分为两类:HeapByteBuffer与DirectByteBuffer。HeapByteBuffer是堆内ByteBuffer,使用byte[]积攒数据,是对数组的包裹,相比简单。DirectByteBuffer是堆外ByteBuffer,直接利用堆外内部存款和储蓄器空间存款和储蓄数据,是NIO高质量的主导设计之壹。本文来分析一下DirectByteBuffer的兑现。
style="font-size: 16px;">
什么样利用DirectByteBuffer
借使急需实例化一个DirectByteBuffer,能够利用java.nio.ByteBuffer#allocateDirect那些主意:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer实例化流程
我们来看一下DirectByteBuffer是怎么着协会,怎么着申请与自由内部存款和储蓄器的。先看看DirectByteBuffer的构造函数:
DirectByteBuffer(int cap) { // package-private
// 初阶化Buffer的多少个焦点属性
super(-1, 0, cap, cap);
// 剖断是或不是需求页面对齐,通过参数-XX: PageAlignDirectMemory调控,暗中认可为false
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 {
// 调用unsafe方法分配内部存款和储蓄器
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
// 分配退步,释放内存
Bits.unreserveMemory(size, cap);
throw x;
}
// 早先化内部存储器空间为0
unsafe.setMemory(base, size, (byte) 0);
// 设置内部存款和储蓄器起先地址
if (pa && (base % ps != 0)) {
address = base ps - (base & (ps - 1));
} else {
address = base;
}
// 使用Cleaner机制注册内部存款和储蓄器回收管理函数
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
提请内部存款和储蓄器前会调用java.nio.Bits#reserveMemory推断是或不是有丰裕的空间可供申请:
// 该格局首要用以推断申请的堆外内部存款和储蓄器是不是超越了用例钦赐的最大值
// 如若还有丰盛空间能够申请,则更新对应的变量
// 如若已经远非空间可以申请,则抛出OOME
// 参数解释:
// size:根据是或不是按页对齐,获得的真正须要申请的内部存款和储蓄器大小
// cap:用户钦命需求的内部存款和储蓄器大小(<=size)
static void reserveMemory(long size, int cap) {
// 因为涉及到更新四个静态计算变量,这里要求Bits类锁
synchronized (Bits.class) {
// 获取最大能够申请的对外内部存储器大小,默许值是6四MB
// 能够经过参数-XX:马克斯DirectMemorySize=<size>设置那么些分寸
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
// -XX:马克斯DirectMemorySize限制的是用户申请的轻重,而不考虑对齐情况
// 所以使用三个变量来总计:
// reservedMemory:真实的当前保留的长空
// totalCapacity:方今用户申请的长空
if (cap <= maxMemory - totalCapacity) {
reservedMemory = size;
totalCapacity = cap;
count ;
return; // 就算空间丰硕,更新总计变量后平素重返
}
}
// 倘诺已经没有丰裕空间,则尝试GC
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
style="font-size: 16px;">Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
// GC后再一次判定,假设依旧未有丰富空间,则抛出OOME
if (totalCapacity cap > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory = size;
totalCapacity = cap;
count ;
}
}
在java.nio.Bits#reserveMemory方法中,假若空间欠缺,会调用System.gc()尝试释放内存,然后再进行判别,若是还是未有丰硕的长空,抛出OOME。
比如分配退步,则须求把预留的总计变量更新回去:
static synchronized void unreserveMemory(long size, int cap) {
if (reservedMemory > 0) {
reservedMemory -= size;
totalCapacity -= cap;
count--;
assert (reservedMemory > -1);
}
}
从地点多少个函数中大家能够赢得新闻:
- 能够由此-XX: PageAlignDirectMemor参数调控堆外内部存储器分配是还是不是供给按页对齐,暗中认可不对齐。
- 如何使用DirectByteBuffer2138a太阳集团。老是申请和释放内需调用调用Bits的reserveMemory或unreserveMemory方法,那多个点子遵照个中维护的总结变量决断当前是或不是还有充裕的长空可供申请,如果有丰裕的长空,更新总计变量,假诺未有充足的空中,调用System.gc()尝试进行垃圾回收,回收后再度展开剖断,倘若照旧没有丰裕的上空,抛出OOME。
- Bits的reserveMemory方法判别是不是有丰裕内部存储器不是决断物理机是不是有丰富内部存款和储蓄器,而是判定JVM运转时,内定的堆外内部存款和储蓄器空间大小是不是有剩余的空中。那几个分寸由参数-XX:马克斯DirectMemorySize=<size>设置。
- 明显有丰硕的空中后,使用sun.misc.Unsafe#allocateMemory申请内部存款和储蓄器
- 提请后的内存空间会被清零
- DirectByteBuffer使用Cleaner机制举行空中回收
能够看来除了推断是不是有丰裕的半空中的逻辑外,核心的逻辑是调用sun.misc.Unsafe#allocateMemory申请内存,我们看一下那些函数是怎么样申请对外内部存款和储蓄器的:
// 申请壹块本地内部存款和储蓄器。内部存款和储蓄器空间是未初叶化的,其内容是心有余而力不足预料的。
// 使用freeMemory释放内部存储器,使用reallocateMemory修改内部存款和储蓄器大小
public native long allocateMemory(long bytes);
// openjdk8/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
style="font-size: 16px;">UnsafeWrapper("Unsafe_AllocateMemory");
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
style="font-size: 16px;">THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
if (sz == 0) {
return 0;
}
sz = round_to(sz, HeapWordSize);
// 调用os::malloc申请内部存款和储蓄器,内部使用malloc函数申请内部存款和储蓄器
void* x = os::malloc(sz, mtInternal);
if (x == NULL) {
style="font-size: 16px;">THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
//Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
return addr_to_java(x);
UNSAFE_END
能够见到sun.misc.Unsafe#allocateMemory使用malloc那么些C标准库的函数来报名内存。
DirectByteBuffer回收流程
在DirectByteBuffer的构造函数的尾声,我们来看了那般的说话:
// 使用Cleaner机制注册内部存款和储蓄器回收管理函数
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
那是应用Cleaner机制进行内部存款和储蓄器回收。因为DirectByteBuffer申请的内部存款和储蓄器是在堆外,DirectByteBuffer自个儿协理保存了内部存款和储蓄器的苗头地址而已,所以DirectByteBuffer的内部存储器占用是由堆内的DirectByteBuffer对象与堆外的呼应内存空间共同构成。堆内的挤占只是极小的1部分,那种对象被号称冰山目的。
堆内的DirectByteBuffer对象自己会被垃圾回收符合规律的管理,然则对外的内部存储器就不会被GC回收了,所以要求八个机制,在DirectByteBuffer回收时,同时回收其堆外申请的内部存款和储蓄器。
Java中可选的性状有finalize函数,可是finalize机制是Java官方不推荐的,官方推荐的做法是应用虚引用来管理目的被回收时的接续处理专门的学业,能够参见JDK源码阅读-Reference。同时Java提供了Cleaner类来简化这些落成,Cleaner是PhantomReference的子类,能够在PhantomReference被参加ReferenceQueue时触发对应的Runnable回调。
style="font-size: 16px;">
DirectByteBuffer正是使用Cleaner机制来促成作者被GC时,回收堆外内部存款和储蓄器的工夫。大家来看一下其回收管理函数是何等兑现的:
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) {
如何使用DirectByteBuffer2138a太阳集团。assert (address != 0);
如何使用DirectByteBuffer2138a太阳集团。this.address = address;
this.size = size;
如何使用DirectByteBuffer2138a太阳集团。this.capacity = capacity;
}
public void run() {
if (address == 0) {
// Paranoia
return;
}
// 使用unsafe方法释放内部存款和储蓄器
unsafe.freeMemory(address);
address = 0;
// 更新总括变量
Bits.unreserveMemory(size, capacity);
}
}
sun.misc.Unsafe#freeMemory方法应用C标准库的free函数释放内部存款和储蓄器空间。同时更新Bits类中的计算变量。
DirectByteBuffer读写逻辑
public ByteBuffer put(int i, byte x) {
unsafe.putByte(ix(checkIndex(i)), ((x)));
return this;
}
public byte get(int i) {
return ((unsafe.getByte(ix(checkIndex(i)))));
}
private long ix(int i) {
return address (i << 0);
}
DirectByteBuffer使用sun.misc.Unsafe#getByte(long)和sun.misc.Unsafe#putByte(long, byte)那五个艺术来读写堆外内部存款和储蓄器空间的钦定地方的字节数据。可是那七个方法本地落成相比复杂,这里就不分析了。
暗许能够报名的堆外内部存款和储蓄器大小
上文提到了DirectByteBuffer申请内部存款和储蓄器前会剖断是不是有丰富的空中可供申请,这么些是在2个钦赐的堆外大小限制的前提下。用户能够透过-XX:MaxDirectMemorySize=<size>那一个参数来调节能够申请多大的DirectByteBuffer内存。不过暗许情形下这些尺寸是稍稍呢?
DirectByteBuffer通过sun.misc.VM#maxDirectMemory来获得那么些值,能够看一下应和的代码:
// A user-settable upper limit on the maximum amount of allocatable direct
// buffer memory. This value may be changed during VM initialization if
// "java" is launched with "-XX:MaxDirectMemorySize=<size>".
//
// The initial value of this field is arbitrary; during JRE initialization
// it will be reset to the value specified on the command line, if any,
// otherwise to Runtime.getRuntime().maxMemory().
//
private static long directMemory = 64 * 1024 * 1024;
// Returns the maximum amount of allocatable direct buffer memory.
// The directMemory variable is initialized during system initialization
// in the saveAndRemoveProperties method.
//
public static long maxDirectMemory() {
return directMemory;
}
此间directMemory私下认可赋值为6四MB,那对外内存的私下认可大小是64MB吗?不是,仔细看注释,注释中说,那几个值会在JRE运转进程中被再一次设置为用户钦定的值,假使用户并未有点名,则会安装为Runtime.getRuntime().maxMemory()。
其一历程产生在sun.misc.VM#saveAndRemoveProperties函数中,这些函数会被java.lang.System#initializeSystemClass调用:
public static void saveAndRemoveProperties(Properties props) {
if (booted)
throw new IllegalStateException("System initialization has completed");
savedProps.putAll(props);
// Set the maximum amount of direct memory. This value is controlled
// by the vm option -XX:MaxDirectMemorySize=<size>.
// The maximum amount of allocatable direct buffer memory (in bytes)
// from the system property sun.nio.MaxDirectMemorySize set by the VM.
// The system property will be removed.
String s = (String)props.remove("sun.nio.MaxDirectMemorySize");
if (s != null) {
if (s.equals("-1")) {
// -XX:MaxDirectMemorySize not given, take default
directMemory = Runtime.getRuntime().maxMemory();
} else {
long l = Long.parseLong(s);
if (l > -1)
directMemory = l;
}
}
//...
}
故而暗中同意意况下,能够报名的DirectByteBuffer大小为Runtime.getRuntime().maxMemory(),而那些值等于可用的最大Java堆大小,也正是大家-Xmx参数钦点的值。
就此最后敲定是:暗中同意情状下,能够申请的最大DirectByteBuffer空间为Java最大堆大小的值。
和DirectByteBuffer有关的JVM选项
依赖上文的辨析,有八个JVM参数与DirectByteBuffer直接相关:
- -XX: PageAlignDirectMemory:钦赐申请的内部存款和储蓄器是还是不是要求按页对齐,默许不对其
- -XX:马克斯DirectMemorySize=<size>,可以报名的最大DirectByteBuffer大小,暗中认可与-Xmx相等
参考资料
- Java Max Direct Memory Size设置 – CSDN博客
- Runtime.getRunTime.maxMemory为何比Xmx钦赐的内存小 – CSDN博客
- JVM源码分析之堆外内部存储器完全解读 – 你假笨
【关于投稿】
要是我们有原创好文投稿,请直接给公号发送留言。回去和讯,查看更加多
责任编辑:
本文由2138com太阳集团发布于国际前线,转载请注明出处:如何使用DirectByteBuffer2138a太阳集团
关键词: 2138com太阳集团 2138a太阳集团