威尼斯人app下载

直接运用堆外内部存款和储蓄器空间存款和储蓄数据【威尼斯人app下载】

九月 23rd, 2019  |  威尼斯注册

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);

}

}

参考资料

  1. 可以通过-XX:+PageAlignDirectMemor参数控制堆外内存分配是否需要按页对齐,默认不对齐。
  2. 每次申请和释放需要调用调用Bits的reserveMemory或unreserveMemory方法,这两个方法根据内部维护的统计变量判断当前是否还有足够的空间可供申请,如果有足够的空间,更新统计变量,如果没有足够的空间,调用System.gc()尝试进行垃圾回收,回收后再次进行判断,如果还是没有足够的空间,抛出OOME。
  3. Bits的reserveMemory方法判断是否有足够内存不是判断物理机是否有足够内存,而是判断JVM启动时,指定的堆外内存空间大小是否有剩余的空间。这个大小由参数-XX:MaxDirectMemorySize=<size>设置。
  4. 确定有足够的空间后,使用sun.misc.Unsafe#allocateMemory申请内存
  5. 申请后的内存空间会被清零
  6. DirectByteBuffer使用Cleaner机制进行空间回收

从上面几个函数中我们可以得到信息:

来源:木杉的博客 ,

style=”font-size: 16px;”>imushan.com/2018/08/29/java/language/JDK源码阅读-DirectByteBuffer/

如何使用DirectByteBuffer

public static ByteBuffer
allocateDirect(int capacity) {

return new
DirectByteBuffer(capacity);

}

如果需要实例化一个DirectByteBuffer,可以使用java.nio.ByteBuffer#allocateDirect这个方法:

在文章JDK源码阅读-ByteBuffer中,我们学习了ByteBuffer的设计。但是他是一个抽象类,真正的实现分为两类:HeapByteBuffer与DirectByteBuffer。HeapByteBuffer是堆内ByteBuffer,使用byte[]存储数据,是对数组的封装,比较简单。DirectByteBuffer是堆外ByteBuffer,直接使用堆外内存空间存储数据,是NIO高性能的核心设计之一。本文来分析一下DirectByteBuffer的实现。

//
该方法主要用于判断申请的堆外内存是否超过了用例指定的最大值

//
如果还有足够空间可以申请,则更新对应的变量

//
如果已经没有空间可以申请,则抛出OOME

// 参数解释:

//
size:根据是否按页对齐,得到的真实需要申请的内存大小

//
cap:用户指定需要的内存大小(<=size)

static void reserveMemory(long size,
int cap) {

//
因为涉及到更新多个静态统计变量,这里需要Bits类锁

synchronized (Bits.class) {

//
获取最大可以申请的对外内存大小,默认值是64MB

//
可以通过参数-XX:MaxDirectMemorySize=<size>设置这个大小

if (!memoryLimitSet && VM.isBooted())
{

maxMemory =
VM.maxDirectMemory();

memoryLimitSet = true;

}

//
-XX:MaxDirectMemorySize限制的是用户申请的大小,而不考虑对齐情况

// 所以使用两个变量来统计:

//
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++;

}

}

DirectByteBuffer就是使用Cleaner机制来实现本身被GC时,回收堆外内存的能力。我们来看一下其回收处理函数是如何实现的:

如果分配失败,则需要把预留的统计变量更新回去:

DirectByteBuffer读写逻辑

和DirectByteBuffer有关的JVM选项

// 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;

}

//
申请一块本地内存。内存空间是未初始化的,其内容是无法预期的。

//
使用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

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对象本身会被垃圾回收正常的处理,但是对外的内存就不会被GC回收了,所以需要一个机制,在DirectByteBuffer回收时,同时回收其堆外申请的内存。

style=”font-size: 16px;”>

  • -XX:+PageAlignDirectMemory:指定申请的内存是否需要按页对齐,默认不对其
  • -XX:MaxDirectMemorySize=<size>,可以申请的最大DirectByteBuffer大小,默认与-Xmx相等

DirectByteBuffer使用sun.misc.Unsafe#getByte(long)和sun.misc.Unsafe#putByte(long,
byte)这两个方法来读写堆外内存空间的指定位置的字节数据。不过这两个方法本地实现比较复杂,这里就不分析了。

DirectByteBuffer回收流程

这里directMemory默认赋值为64MB,那对外内存的默认大小是64MB吗?不是,仔细看注释,注释中说,这个值会在JRE启动过程中被重新设置为用户指定的值,如果用户没有指定,则会设置为Runtime.getRuntime().maxMemory()。

//
使用Cleaner机制注册内存回收处理函数

cleaner = Cleaner.create(this, new
Deallocator(base, size, cap));

如果大家有原创好文投稿,请直接给公号发送留言。返回搜狐,查看更多

可以看出sun.misc.Unsafe#allocateMemory使用malloc这个C标准库的函数来申请内存。

  • Java Max Direct Memory Size设置 – CSDN博客
  • Runtime.getRunTime.maxMemory为啥比Xmx指定的内存小 – CSDN博客
  • JVM源码分析之堆外内存完全解读 – 你假笨

static synchronized void
unreserveMemory(long size, int cap) {

if (reservedMemory > 0) {

reservedMemory -= size;

totalCapacity -= cap;

count–;

assert (reservedMemory > -1);

}

}

上文提到了DirectByteBuffer申请内存前会判断是否有足够的空间可供申请,这个是在一个指定的堆外大小限制的前提下。用户可以通过-XX:MaxDirectMemorySize=<size>这个参数来控制可以申请多大的DirectByteBuffer内存。但是默认情况下这个大小是多少呢?

根据上文的分析,有两个JVM参数与DirectByteBuffer直接相关:

sun.misc.Unsafe#威尼斯注册自动送37,freeMemory方法使用C标准库的free函数释放内存空间。同时更新Bits类中的统计变量。

这个过程发生在sun.misc.VM#saveAndRemoveProperties函数中,这个函数会被java.lang.System#initializeSystemClass调用:

在DirectByteBuffer的构造函数的最后,我们看到了这样的语句:

style=”font-size: 16px;”>

可以看出除了判断是否有足够的空间的逻辑外,核心的逻辑是调用sun.misc.Unsafe#allocateMemory申请内存,我们看一下这个函数是如何申请对外内存的:

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);

威尼斯人app下载,}

所以最终结论是:默认情况下,可以申请的最大DirectByteBuffer空间为Java最大堆大小的值。

Java中可选的特性有finalize函数,但是finalize机制是Java官方不推荐的,官方推荐的做法是使用虚引用来处理对象被回收时的后续处理工作,可以参考JDK源码阅读-Reference。同时Java提供了Cleaner类来简化这个实现,Cleaner是PhantomReference的子类,可以在PhantomReference被加入ReferenceQueue时触发对应的Runnable回调。

vnsr威尼斯城官网登入,DirectByteBuffer通过sun.misc.VM#maxDirectMemory来获取这个值,可以看一下对应的代码:

责任编辑:

默认可以申请的堆外内存大小

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;

}

所以默认情况下,可以申请的DirectByteBuffer大小为Runtime.getRuntime().maxMemory(),而这个值等于可用的最大Java堆大小,也就是我们-Xmx参数指定的值。

【关于投稿】

我们来看一下DirectByteBuffer是如何构造,如何申请与释放内存的。先看看DirectByteBuffer的构造函数:

在java.nio.Bits#reserveMemory方法中,如果空间不足,会调用System.gc()尝试释放内存,然后再进行判断,如果还是没有足够的空间,抛出OOME。

这是使用Cleaner机制进行内存回收。因为DirectByteBuffer申请的内存是在堆外,DirectByteBuffer本身支持保存了内存的起始地址而已,所以DirectByteBuffer的内存占用是由堆内的DirectByteBuffer对象与堆外的对应内存空间共同构成。堆内的占用只是很小的一部分,这种对象被称为冰山对象。

DirectByteBuffer实例化流程

申请内存前会调用java.nio.Bits#reserveMemory判断是否有足够的空间可供申请:

原标题:JDK 源码阅读 : DirectByteBuffer

标签:,

Your Comments

近期评论

    功能


    网站地图xml地图