java直接(堆外)内存使用详解-mile米乐体育
本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明:
相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明
希望对想使用直接内存的朋友,提供点快捷的参考。
数据类型
下面这些,都是在使用directbuffer
中必备的一些常识,暂作了解吧!如果想要深入理解,可以看看下面参考的那些博客。
基本类型长度
在java中有很多的基本类型,比如:
byte
,一个字节是8位bit,也就是1bshort
,16位bit,也就是2bint
,32位bit,也就是4blong
, 64位bit,也就是8bchar
,16位bit,也就是2bfloat
,32位bit,也就是4bdouble
,64位bit,也就是8b
不同的类型都会按照自己的位数来存储,并且可以自动进行转换提升。
byte
、char
、short
都可以自动提升为int
,如果操作数有long
,就会自动提升为long
,float
和double
也是如此。
大端小端
由于一个数据类型可能有很多个字节组成的,那么它们是如何摆放的。这个是有讲究的:
- 大端:低地址位 存放 高有效字节
- 小端:低地址位 存放 低有效字节
举个例子,一个char
是有两个字节组成的,这两个字节存储可能会显示成如下的模样,比如字符a
:
低地址位 高地址位 大端; 00 96 小端: 96 00
string与new string的区别
再说说"hello"
和new string("hello")
的区别:
如果是"hello"
,jvm会先去共享的字符串池中查找,有没有"hello"
这个词,如果有直接返回它的引用;如果没有,就会创建这个对象,再返回。因此,"a" "b"
相当于存在3个对象,分别是"a"
、"b"
、"ab"
。
而new string("hello")
,则省去了查找的过程,直接就创建一个hello
的对象,并且返回引用。
读写数据
在直接内存中,通过allocatedirect(int byte_length)
申请直接内存。这段内存可以理解为一段普通的基于byte
的数组,因此插入和读取都跟普通的数组差不多。
只不过提供了基于不同数据类型的插入方法,比如:
- put(byte) 插入一个byte
- put(byte[]) 插入一个byte数组
- putchar(char) 插入字符
- putint(int) 插入int
- putlong(long) 插入long
等等….详细的使用方法,也可以参考下面的图片:
对应读取数据,跟写入差不多:
注意所有没有index参数的方法,都是按照当前position的位置进行操作的。
下面看看什么是position,还有什么其他的属性吧!
基本的属性值
它有几个关键的指标:
mark-->position-->limit-->capacity
另外,还有remaining=limit-position
。
先说说他们的意思吧!
当前位置——position
position是当前数组的指针,指示当前数据位置。举个例子:
bytebuffer buffer = bytebuffer.allocatedirect(1024); buffer.putchar('a'); system.out.println(buffer); buffer.putchar('c'); system.out.println(buffer); buffer.putint(10); system.out.println(buffer);
由于一个char是2个字节,一个int是4个字节,因此position的位置分别是:
2,4,8
注意,position的位置是插入数据的当前位置,如果插入数据,就会自动后移。
也就是说,如果存储的是两个字节的数据,position的位置是在第三个字节上,下标就是2。
java.nio.directbytebuffer[pos=2 lim=1024 cap=1024] java.nio.directbytebuffer[pos=4 lim=1024 cap=1024] java.nio.directbytebuffer[pos=8 lim=1024 cap=1024]
position可以通过position()获得,也可以通过position(int)设置。
//position(int)方法的源码 public final buffer position(int newposition) { if ((newposition > limit) || (newposition < 0)) throw new illegalargumentexception(); position = newposition; if (mark > position) mark = -1; return this; }
注意:position的位置要比limit小,比mark大
空间容量——capacity
capacity
是当前申请的直接内存的容量,它是申请后就不会改变的。
capacity则可以通过capacity()方法获得。
限制大小——limit
我们可能想要改变这段直接内存的大小,因此可以通过一个叫做limit的属性设置。
limit则可以通过limit()获得,通过limit(int)进行设置。
注意limit要比mark和position大,比capacity小。
//limit(int)方法的源码 public final buffer limit(int newlimit) { if ((newlimit > capacity) || (newlimit < 0)) throw new illegalargumentexception(); limit = newlimit; if (position > limit) position = limit; if (mark > limit) mark = -1; return this; }
标记位置——mark
mark,就是一个标记为而已,记录当前的position的值。常用的场景,就是记录某一次插入数据的位置,方便下一次进行回溯。
- 可以使用
mark()
方法进行标记, - 使用
reset()
方法进行清除, - 使用
rewind()
方法进行初始化//mark方法标记当前的position,默认为-1 public final buffer mark() { mark = position; return this; } //reset方法重置mark的位置,position的位置,不能小于mark的位置,否则会出错 public final buffer reset() { int m = mark; if (m < 0) throw new invalidmarkexception(); position = m; return this; } //重置mark为-1.position为0 public final buffer rewind() { position = 0; mark = -1; return this; }
使用案例
bytebuffer buffer = bytebuffer.allocatedirect(1024); buffer.putchar('a'); buffer.putchar('c'); system.out.println("插入完数据 " buffer); buffer.mark();// 记录mark的位置 buffer.position(30);// 设置的position一定要比mark大,否则mark无法重置 system.out.println("reset前 " buffer); buffer.reset();// 重置reset ,reset后的position=mark system.out.println("reset后 " buffer); buffer.rewind();//清除标记,position变成0,mark变成-1 system.out.println("清除标记后 " buffer);
可以看到如下的运行结果:
插入完数据 java.nio.directbytebuffer[pos=4 lim=1024 cap=1024] reset前 java.nio.directbytebuffer[pos=30 lim=1024 cap=1024] reset后 java.nio.directbytebuffer[pos=4 lim=1024 cap=1024] 清除标记后 java.nio.directbytebuffer[pos=0 lim=1024 cap=1024]
剩余空间——remaing
remaing
则表示当前的剩余空间:
public final int remaining() { return limit - position; }
读写实践
写操作主要就是按照自己的数据类型,写入到直接内存中,注意每次写入数据的时候,position都会自动加上写入数据的长度,指向下一个该写入的起始位置:
下面看看如何写入一段byte[]或者字符串:
bytebuffer buffer = bytebuffer.allocatedirect(10); byte[] data = {1,2}; buffer.put(data); system.out.println("写byte[]后 " buffer); buffer.clear(); buffer.put("hello".getbytes()); system.out.println("写string后 " buffer);
输出的内容为:
写byte[]后 java.nio.directbytebuffer[pos=2 lim=10 cap=10] 写string后 java.nio.directbytebuffer[pos=5 lim=10 cap=10]
读的时候,可以通过一个外部的byte[]
数组进行读取。由于没有找到直接操作直接内存的方法: 因此如果想在jvm应用中使用直接内存,需要申请一段堆中的空间,存放数据。
如果有更好的方法,还请留言。
bytebuffer buffer = bytebuffer.allocatedirect(10); buffer.put(new byte[]{1,2,3,4}); system.out.println("刚写完数据 " buffer); buffer.flip(); system.out.println("flip之后 " buffer); byte[] target = new byte[buffer.limit()]; buffer.get(target);//自动读取target.length个数据 for(byte b : target){ system.out.println(b); } system.out.println("读取完数组 " buffer);
输出为
刚写完数据 java.nio.directbytebuffer[pos=4 lim=10 cap=10] flip之后 java.nio.directbytebuffer[pos=0 lim=4 cap=10] 1 2 3 4 读取完数组 java.nio.directbytebuffer[pos=4 lim=4 cap=10]
常用方法
上面的读写例子中,有几个常用的方法:
clear()
这个方法用于清除mark和position,还有limit的位置:
public final buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
flip()
这个方法主要用于改变当前的position为limit,主要是用于读取操作。
public final buffer flip() { limit = position; position = 0; mark = -1; return this; }
compact()
这个方法在读取一部分数据的时候比较常用。
它会把当前的position移到0,然后position 1移到1。
public bytebuffer compact() { int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); unsafe.copymemory(ix(pos), ix(0), rem << 0); position(rem); limit(capacity()); discardmark(); return this; }
比如一段空间内容为:
123456789
当position的位置在2时,调用compact方法,会变成:
345678989
isdirect()
这个方法用于判断是否是直接内存。如果是返回true,如果不是返回false。
rewind()
这个方法用于重置mark标记:
public final buffer rewind() { position = 0; mark = -1; return this; }
参考
1 java基本数据类型
2 java中大端与小端