本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明:

相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明

希望对想使用直接内存的朋友,提供点快捷的参考。

数据类型

下面这些,都是在使用DirectBuffer中必备的一些常识,暂作了解吧!如果想要深入理解,可以看看下面参考的那些博客。

基本类型长度

在Java中有很多的基本类型,比如:

  • byte,一个字节是8位bit,也就是1B
  • short,16位bit,也就是2B
  • int,32位bit,也就是4B
  • long, 64位bit,也就是8B
  • char,16位bit,也就是2B
  • float,32位bit,也就是4B
  • double,64位bit,也就是8B

不同的类型都会按照自己的位数来存储,并且可以自动进行转换提升。

bytecharshort都可以自动提升为int,如果操作数有long,就会自动提升为longfloatdouble也是如此。

大端小端

由于一个数据类型可能有很多个字节组成的,那么它们是如何摆放的。这个是有讲究的:

  • 大端:低地址位 存放 高有效字节
  • 小端:低地址位 存放 低有效字节

举个例子,一个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中大端与小端

Java直接(堆外)内存使用详解的更多相关文章

  1. java程序运行时内存分配详解

    java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下   一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...

  2. Java进程堆外内存(off heap)大小

    一.使用ByteBuffer.allocateDirect分配的off heap内存大小 本机进程 在Jvisualvm中安装 Mbeans插件.然后查看java.nio/BufferPool/dir ...

  3. [Java]Java类和对象内存分配详解

    描述 代码说明: 一.当Person p1 = new Person();第一次被调用时需要做两件事: 1.先判断类加载器是否加载过Person类,如果没有则加载到Person类型到方法区 2.在堆中 ...

  4. JAVA使用堆外内存导致swap飙高

    https://github.com/nereuschen/blog/issues/29 堆内内存分析一般用Memory Analyzer Tool http://tivan.iteye.com/bl ...

  5. java程序运行时内存分配详解 (转)

    转自:http://www.tuicool.com/articles/uU77v2 一.  基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Ja ...

  6. JAVA中堆栈和内存分配详解(摘抄)

    在Java中,有六个不同的地方可以存储数据: 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存 ...

  7. Java NIO 堆外内存与零拷贝

    一.直接缓存 这个例子的区别就是 ByteBuffer.allocateDirect(512); 进入allocateDirect方法 进入DirectByteBuffer构造函数 Native方法: ...

  8. Netty之Java堆外内存扫盲贴

    Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现.但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接 ...

  9. String类内存空间详解

    java.lang.String类内存问题详解 字符串理解的难点在于其在堆内存空间上的特殊性,字符串String对象在堆内存上有两种空间: 字符串池(String pool):特殊的堆内存,专门存放S ...

  10. cassandra 堆外内存管理

    为什么需要堆外内存呢 单有一些大内存对象的时候,JVM进行垃圾回收时需要收集所有的这些对象的内存也.增加了GC压力.因此需要使用堆外内存. java 分配堆外内存 org.apache.cassand ...

随机推荐

  1. 一次性事务和CTE插入数据的比较

    有时要构造一些数据来做测试数据,像下面这样: IF OBJECT_ID(N'T14') IS NOT NULL BEGIN DROP TABLE T14 END GO CREATE TABLE T14 ...

  2. openjdk 完全编译指南

    从openjdk.java.net下载openjdk的软件包,你就获得了所有相关的源码. 强烈建议首先仔细看懂 README-builds.html 指南. 在执行 make all 之前,首先要 执 ...

  3. tyvj1202 数数食物链

    描述 TsyD学习了生物的生态环境那一张后,老师留了一项作业,就是给一张食物网,求所有食物链的总数.(从最低营养级生物(它不能吃任何其他的生物)开始到最高营养级(它不能被任何其他生物吃) 叫做一条食物 ...

  4. 个性化设置phpMyAdmin,去掉“以树形显示数据库”,禁用“发送错误报告”

    个性化设置phpMyAdmin 在使用phpMyAdmin 3.5.8.2时,发现: 如果数据库有相同的前缀,左边数据库导航会把前缀合并,即所谓的“以树形显示数据库”,真的有点不习惯,如下图所示: 不 ...

  5. PHP输出XML文件函数

    PHP输出XML文件函数 function xml_out($content, $charset = 'utf-8') { @header("Expires: -1"); @hea ...

  6. PF_INET 和 AF_INET 的区别

    在写网络程序的时候,建立TCP socket: sock = socket(PF_INET, SOCK_STREAM, 0); 然后再绑定本地地址或连接远程地址时需要初始化sockaddr_in结构, ...

  7. 交叉编译中的build、host和target

    build.host和target    在交叉编译中比较常见的一些参数就是build.host和target了,正确的理解这三者的含义对于交叉编译是非常重要的,下面就此进行解释 --build=编译 ...

  8. 02第一个OC程序

    一.打开XCode4.6.3,新建项目 二.选择Application下的命名行项目 三.输入项目名称,选择Foundation框架创建项目,点击Next 四.二话不说.点击Run.启动我们创建的项目 ...

  9. hash模块 hashlib 和hmac

    hashlib模块 用于加密相关的操作,代替md5模块和sha模块,主要提供SHA1,SHA224,SSHA256,SHA384,SHA512,MD5算法 直接看代码案例: ---------md5- ...

  10. vim 使用

    vim有三种模式:输入模式,命令模式,底行模式,使用esc进入命令模式,在命令模式下按英文的冒号,进入底行模式:命令行模式下按i进入输入模式.vim编辑文件是将文件内容复制到缓冲区显示在屏幕上. vi ...