imushan.com/2018/08/29/java/language/JDK源码阅读-DirectByteBuffer/ 在小说JDK源码阅读-ByteBuffer中,大家学习了ByteBuffer的" />

直接使用堆外内存空间存储数据

2019-07-21 11:03 来源:未知

原标题:JDK 源码阅读 : DirectByteBuffer

发源:木杉的博客 ,

style="font-size: 16px;">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) {

// 获取最大能够申请的对外内部存款和储蓄器大小,暗中同意值是64MB

// 能够因此参数-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);

直接使用堆外内存空间存储数据。}

}

从下边多少个函数中我们得以博得音信:

  1. 能够透过-XX: PageAlignDirectMemor参数调控堆外内存分配是还是不是要求按页对齐,暗中同意不对齐。
  2. 老是申请和刑满释放解除劳教内需调用调用Bits的reserveMemory或unreserveMemory方法,那七个方法根据其中维护的计算变量剖断当前是还是不是还也许有丰盛的上空可供申请,借使有足够的长空,更新总括变量,若无丰盛的半空中,调用System.gc()尝试举办垃圾回收,回收后再一次进行判定,要是依旧未有丰盛的上空,抛出OOME。
  3. Bits的reserveMemory方法推断是不是有丰硕内部存款和储蓄器不是判断物理机是或不是有丰硕内部存款和储蓄器,而是判别JVM运转时,钦点的堆外内部存储器空间大小是还是不是有剩余的空中。那几个分寸由参数-XX:马克斯DirectMemorySize=<size>设置。
  4. 规定有充裕的空中后,使用sun.misc.Unsafe#allocateMemory申请内部存款和储蓄器
  5. 申请后的内部存款和储蓄器空间会被清零
  6. 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对象与堆外的附和内部存款和储蓄器空间共同构成。堆内的据有只是极小的一有的,这种对象被堪称冰山目的。

堆内的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) {

assert (address != 0);

this.address = address;

this.size = size;

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申请内部存款和储蓄器前会决断是不是有丰硕的长空可供申请,这一个是在一个钦点的堆外大小限制的前提下。用户能够透过-XX:马克斯DirectMemorySize=<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默许赋值为64MB,那对外内部存款和储蓄器的暗中认可大小是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: Page阿里gnDirectMemory:内定申请的内部存款和储蓄器是不是须求按页对齐,私下认可不对其
  • -XX:马克斯DirectMemorySize=<size>,能够申请的最大DirectByteBuffer大小,暗中同意与-Xmx相等

仿效资料

  • Java Max Direct Memory Size设置 – CSDN博客
  • Runtime.getRunTime.maxMemory为什么比Xmx钦命的内存小 – CSDN博客
  • JVM源码深入分析之堆外内部存款和储蓄器完全解读 – 你假笨

【关于投稿】

只要大家有原创好文投稿,请间接给公号发送留言。回到乐乎,查看越来越多

主编:

TAG标签: 40469太阳集团
版权声明:本文由40469太阳集团发布于国际前线,转载请注明出处:直接使用堆外内存空间存储数据