在java.nio.Bits#reserveMemory方法中,如果空间不足,会调用System.gc()尝试内存,然后再进行判断,如果还是没有足够的空间,抛出OOME。
可以通过-:+PageAlignDirectMemor参数控制堆外内存分配是否需要按页对齐,默认不对齐。
每次申请和需要调用调用Bits的reserveMemory或unreserveMemory方法,这两个方法根据内部的统计变量判断当前是否还有足够的空间可供申请,如果有足够的空间,更新统计变量,如果没有足够的空间,调用System.gc()尝试进行垃圾回收,回收后再次进行判断,如果还是没有足够的空间,抛出OOME。
Bits的reserveMemory方法判断是否有足够内存不是判断物理机是否有足够内存,而是判断JVM启动时,指定的堆外内存空间大小是否有剩余的空间。这个大小由参数-:MaxDirectMemorySize=size设置。
确定有足够的空间后,使用sun.misc.Unsafe#allocateMemory申请内存
可以看出除了判断是否有足够的空间的逻辑外,核心的逻辑是调用sun.misc.Unsafe#allocateMemory申请内存,我们看一下这个函数是如何申请对外内存的:
这是使用Cleaner机制进行内存回收。因为DirectByteBuffer申请的内存是在堆外,DirectByteBuffer本身支持保存了内存的起始地址而已,所以DirectByteBuffer的内存占用是由堆内的DirectByteBuffer对象与堆外的对应内存空间共同构成。堆内的占用只是很小的一部分,这种对象被称为冰山对象。
堆内的DirectByteBuffer对象本身会被垃圾回收正常的处理,但是对外的内存就不会被GC回收了,所以需要一个机制,在DirectByteBuffer回收时,同时回收其堆外申请的内存。
DirectByteBuffer就是使用Cleaner机制来实现本身被GC时,回收堆外内存的能力。我们来看一下其回收处理函数是如何实现的:
上文提到了DirectByteBuffer申请内存前会判断是否有足够的空间可供申请,这个是在一个指定的堆外大小的前提下。用户可以通过-:MaxDirectMemorySize=size这个参数来控制可以申请多大的DirectByteBuffer内存。但是默认情况下这个大小是多少呢?
这里directMemory默认赋值为64MB,那对外内存的默认大小是64MB吗?不是,仔细看注释,注释中梦见钞票说,这个值会在JRE启动过程中被重新设置为用户指定的值,如果用户没有指定,则会设置为Runtime.getRuntime().maxMemory()。
所以最终结论是:默认情况下,可以申请的最大DirectByteBuffer空间为Java最大堆大小的值。
本文由来源于财鼎国际(www.hengpunai.cn)
网友评论 ()条 查看