概述
JDK5 之后才出现了堆外内存得API给到开发进行调用,那么我们为什么要使用堆外内存呢?
- 加速GC回收,大量对象产生在堆内,GC回收得压力是很大得
- 更自由、更高效得使用整个计算机内存
- 更高性能得跨进程数据通信,避免了主内存得多次copy
使用堆外内存需要考虑哪些问题呢?
- 准确得时间释放无需在使用得堆外内存
带着上面得问题我们接着往下看
Java 堆外内存得操作方式
- 利用unsafe直接操作(危险性比较高,官方不推荐)
- 利用NIO得ByteBuffer,JVM会进行堆外内存管理,当堆内得引用对象被回收时会自动回收相关得堆外内存(推荐)
DirectByteBuffer 对象作为这块内存的引用进行操作,ByteBuffer 提供了如下常用方法来跟堆外内存打交道:
public static ByteBuffer allocateDirect(int capacity)
- 分配堆外内存,返回一个
DirectByteBuffer
堆外内存对象return new DirectByteBuffer(capacity);
- 分配堆外内存,返回一个
public abstract ByteBuffer put(byte b);
- 向堆外内存中存放一个字节
public abstract byte get();
- 从堆外内存中读取一个字节
public final ByteBuffer put(byte[] src)
- 向堆外内存中存放一个字节数组
public ByteBuffer get(byte[] dst)
- 从堆外内存中读取一个字节数组
public abstract ByteBuffer putInt(int value);
- 向堆外内存中存放一个
int
- 向堆外内存中存放一个
public abstract int getInt();
- 从堆外内存中读取一个
int
- 从堆外内存中读取一个
public abstract IntBuffer asIntBuffer()
- 转换为一个
IntBuffer
- 转换为一个
public abstract ByteBuffer putLong(long value);
同上,以此类推public abstract boolean isDirect();
- 判断是否为堆外内存
ByteBuffer 包含了如下的几个属性:
private int mark = -1;
:标记位置,记录当前position
的值private int position = 0;
:当前位置private int limit;
:限制大小private int capacity;
:空间容量- 基本关系
mark <= position <= limit <= capacity
public class ByteBufferTest {
public static void main(String[] args){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect( 1024 );
byteBuffer.putChar( 'A' );
byteBuffer.putLong( 333l );
byteBuffer.position(0);
System.out.println(byteBuffer.getChar());
System.out.println(byteBuffer.getLong());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.position());
}
}
堆外内存的设置
堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize
重新设定。
当使用达到了阈值的时候将调用 System.gc
来做一次 Full GC,以此来回收掉没有被使用的堆外内存。
堆外内存的分配
在 DirectByteBuffer
中,首先向 Bits
类申请额度,Bits
类有一个全局的 totalCapacity
变量,记录着全部 DirectByteBuffer
的总大小,每次申请,都先看看是否超限:
- 如果已经超限,会主动执行
Sytem.gc()
,期待能主动回收一点堆外内存。然后休眠一百毫秒,看看totalCapacity
降下来没有,如果内存还是不足,就抛出大家最头痛的 OOM 异常。 - 如果额度被批准,就调用大名鼎鼎的
sun.misc.Unsafe
去分配内存,返回内存基地址,Unsafe
的 C++实现,标准的malloc
。然后再调一次Unsafe
把这段内存给清零。
堆外内存基于 GC 的回收
存在于堆内的 DirectByteBuffer
对象很小,只存着基地址和大小等几个属性,和一个 Cleaner
,但它代表着后面所分配的一大段内存,是所谓的冰山对象。
通过前面说的 Cleaner
,堆内的 DirectByteBuffer
对象被 GC 时,它背后的堆外内存也会被回收。
这里可以看到一种尴尬的情况,因为 DirectByteBuffer
本身的个头很小,只要熬过了 Young GC,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发 Full GC,如果没有别的大块头进入老生代触发Full GC,就一直在那耗着,占着一大片堆外内存不释放。
这时,就只能靠前面提到的申请额度超限时触发的 System.gc()
来救场了。
最后
以上就是孝顺玫瑰为你收集整理的Java 堆外内存操作和使用场景的全部内容,希望文章能够帮你解决Java 堆外内存操作和使用场景所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复