GVKun编程网logo

JDK ByteBuffer & MINA IoBuffer 总结&比较(bytebuffer java)

15

以上就是给各位分享JDKByteBuffer&MINAIoBuffer总结&比较,其中也会对bytebufferjava进行解释,同时本文还将给你拓展.net–在哪里IBufferbyte[].AsB

以上就是给各位分享JDK ByteBuffer & MINA IoBuffer 总结&比较,其中也会对bytebuffer java进行解释,同时本文还将给你拓展.net – 在哪里IBuffer byte [] .AsBuffer的扩展方法?、24、vb2_buffer和videobuf_buffer比较分析、AudioRecord.read (buffer, 0, BUFFER_SIZE); 得到的 buffer 中的数据的单位?、Buffer的创建及使用源码分析——ByteBuffer为例等相关知识,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

本文目录一览:

JDK ByteBuffer & MINA IoBuffer 总结&比较(bytebuffer java)

JDK ByteBuffer & MINA IoBuffer 总结&比较(bytebuffer java)

    • Position(Mark)<=limit<=capacity
    • position==limit时就没有字节可读写了
    • 每次getput都将增加position
    • 重置mark就是设置mark=-1
  • JDK   ByteBuffer

    属性:

    Mark

    上次position的快照

    Position

    当前读写索引未知

    Limit

    缓冲区限制

    Capacity

    缓冲区能力

    Offset

    偏移量

    说明:

    方法:

    Limit(int)

    如果position>limit, position = limit,如果mark>limit, 重置mark

    Mark()

    取当前的position的快照标记mark

    Reset()

    恢复position到先前标记的mark

    Clear()

    limit=capacity , position=0,重置mark,但是不清空数据,为了从头开始put做准备,其实就是清空数据,因为你put就覆盖了原来的数据

    Rewind()

    position=0,重置mark,一系列写操作后,为了从头开始get做准备,和clear()有用途上的区别,他大部分是用来从头开始读取,而clear是大部分用来重头开始填充,就是清理的意思

    Flip()

    limit=position , position=0,重置mask,为了将buf写出做好准备,一般是结束buf操作,将buf写入输出流时调用,这个必须要调用,否则极有可能position!=limit,导致position后面没有数据,每次写入数据到输出流时,必须确保position=limit

    Remaining()

    返回limit-position,返回缓冲器中的剩余字节

    Wrap(byte[])

    组装到新的buffercapacity=limit=byte[].lengthposition=0 重置mark

    Slice()

    分割缓冲器,将remaining的空间形成一个新的buffer,新的position=0limit=capacity=remaining,重置mark,和主缓冲区内容共享,其它都独立

    Duplicate()

    复制缓冲区,内容共享,其它都独立

    asReadOnlyBuffer()

    和duplicate一样,只是不可写

    Compact()

    positionlimit之间的字节移到最前面,position=limit-position,这就是这里的压缩的意思,一般是结束buf操作,将buf写入输出流时调用

    Position(int)

    position=newPosition,如果position<mark,重置mark

    Remaining()

    返回positionlimit之间的字节数

JDK ByteBuffer

Mina IoBuffer

动态扩展capacity

支持String读写

线程安全

可主动释放缓冲区占用内存

.net – 在哪里IBuffer byte [] .AsBuffer的扩展方法?

.net – 在哪里IBuffer byte [] .AsBuffer的扩展方法?

在构建期间,他们提到要获取一个IBuffer,有一个名为AsBuffer()的字节数组的扩展方法.不幸的是,他们没有提到它隐藏的命名空间,似乎不在我添加的许多内容中.有没有人发现隐藏在哪里?
命名空间是System.Runtime.InteropServices.WindowsRuntime.

VS对象浏览器是您的朋友 – 请务必相应地设置框架切换器.对于Metro应用程序中可用的.NET库,您需要“.NET Framework Core 4.5”.

24、vb2_buffer和videobuf_buffer比较分析

24、vb2_buffer和videobuf_buffer比较分析

看韦东山视频第三期摄像头驱动中构造了自己的vivi驱动,但是使用的videoBuf结构体,新的版本用的是vb2_buffer结构,我机器上(ubuntu12.04)使用的内核是linux3.2,看了看改动还是挺大的,自己看代码自己理解了下:

        首先是韦东山老师总结的摄像头驱动的架构如下
 
摄像头驱动程序必需的11个ioctl:
    // 表示它是一个摄像头设备
 .vidioc_querycap      = vidioc_querycap,
    /* 用于列举、获得、测试、设置摄像头的数据的格式 */
 .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
 .vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
 .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
 .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
    /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
 .vidioc_reqbufs       = vidioc_reqbufs,
 .vidioc_querybuf      = vidioc_querybuf,
 .vidioc_qbuf          = vidioc_qbuf,
 .vidioc_dqbuf         = vidioc_dqbuf,
 // 启动/停止
 .vidioc_streamon      = vidioc_streamon,
 .vidioc_streamoff     = vidioc_streamoff,
     
分析数据的获取过程:
1. 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
2. 查询映射缓冲区:  ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区 
3. 把缓冲区放入队列: ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列         
4. 启动摄像头  ioctl(4, VIDIOC_STREAMON 
5. 用select查询是否有数据  
6. 有数据后从队列里取出缓冲区  ioctl(4, VIDIOC_DQBUF
7. 应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据    就去读对应的地址(该地址来自前面的mmap) 
 
对于架构都是一样的,有关于VB2_buffer 的主要是数据的获取过程,根据韦东山老师的视频中分析步骤分析如下:
1,首先是创建并初始化一个vb2_queue结构体 ,
        static   struct vb2_queue    Myvivi_vb2_queue;
 
        struct vb2_queue  *q = &Myvivi_vb2_queue;
         q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 类型
         q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;  // 该队列支持的模式
         q->drv_priv = dev;  // 自定义模式
         q->buf_struct_size = sizeof(struct vivi_buffer);  // 将vb2_buffer结构体封装到我们自己的buffer中,此为我们自己的buffer的size
         q->ops = &vivi_video_qops;   
         q->mem_ops = &vb2_vmalloc_memops;     // 
        vb2_queue_init(q);
    其中vivi_video_qops 是 队列的操作函数,以后的REQBUFS 等请求会调用到此中的函数,其结构如下
static struct vb2_ops vivi_video_qops = {
 .queue_setup = queue_setup,   // 必须有,vb2_queue_init中会判断
 .buf_init = buffer_init,
 .buf_prepare = buffer_prepare,
 .buf_finish = buffer_finish,
 .buf_cleanup = buffer_cleanup,
 .buf_queue = buffer_queue,  // 必须有,vb2_queue_init中会判断
 .start_streaming = start_streaming,
 .stop_streaming = stop_streaming,
 .wait_prepare = vivi_unlock,
 .wait_finish = vivi_lock,
};
      其中vb2_vmalloc_memops用于有关此队列中mem分配问题,其中的函数一般不需要我们自己写,使用默认
const struct vb2_mem_ops vb2_vmalloc_memops = {
     .alloc = vb2_vmalloc_alloc,
     .put = vb2_vmalloc_put,
     .vaddr = vb2_vmalloc_vaddr,
     .mmap = vb2_vmalloc_mmap,
     .num_users = vb2_vmalloc_num_users,
};
 
2,初始化完成后,调用VIDIOC_REQBUFS  请求系统分配缓冲区,该函数的调用过程如下
    vb2_reqbufs  ===》
            q->ops->queue_setup此函数允许我们的驱动函数自定义分配空间的大小
             __vb2_queue_alloc  分配vivi_buffer结构体的空间(缓存区头部信息), 如果使用的是V4L2_MEMORY_MMAP类型则 调用==>__vb2_buf_mem_alloc  ==> q->mem_ops->alloc  即vb2_vmalloc_alloc 分配空间,将分配的空间指向vb2_buffer->planes[0].mem_priv ,该指针保存着分配到的空间,该指针指向vb2_vmalloc_buf结构体
韦东山老师视频中讲的video_buf中,讲到在这不真正的分配空间,但是在vb2中此时却已经分配了空间,这是我的理解
 
3,查询映射缓冲区 VIDIOC_QUERYBUF , 返回实际上分配到的buffer,
      查询分配好的缓存区,返回v4l2_buffer结构,设置vb->state 
 
4,使用mmap
    vb2_mmap ==》q->mem_ops->mmap   即  vb2_vmalloc_mmap  用于映射,将上面分配好的vb2_buffer->planes[0].mem_priv指向的空间重映射到mmap参数中的用户空间
 
5,把缓冲区放入队列: VIDIOC_QBUF     
        vb2_qbuf    将 list_add_tail(&vb->queued_entry, &q->queued_list);    将vb2_buffer 放入队列q的queued_list中
        设置vb->state = VB2_BUF_STATE_PREPARED;
 
6,  启动摄像头   VIDIOC_STREAMON 
    vb2_streamon 
        q->streaming = 1;   
// 如果 q->queued_list 中部位空,即有qbuf没有被处理 调用__enqueue_in_driver ()
 
7, 用select查询是否有数据  会调用poll函数   
       vb2_poll   等待 q->done_list 中有数据,   
 
8, 怎样往 q->done_list 中添加数据呢 ?
每次调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表, 在vivi中 周期性的调用函数向这个列表中的vb缓冲区中添加数据 即 向vb2_buffer->planes中添加数据 ,然后后调用  
vb2_buffer_done(&vb, VB2_BUF_STATE_DONE);  ==》 list_add_tail(&vb->done_entry, &q->done_list);  将vivi驱动中的vb2 放入 q->done_list中 ,然后设置vb->state = VB2_BUF_STATE_DONE;
最后wake_up(&q->done_wq);  唤醒poll中休眠的进程。
 
9,调用VIDIOC_DQBUF, 从队列里取出缓冲区
        vb2_dqbuf  ==> __vb2_get_done_vb  将q->done_list 中的vb2_buffer中提出来,然后 将vb2_buffer中的v4l2_buffer信息返回,并将其从q->done_list 中删除
 
10,应用程序将数据取出来(mmap的空间)
 
总结:    
结构体如下 
struct vb2_buffer {
 struct v4l2_buffer v4l2_buf;    // 里面有该vb2中数据的信息
 struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];
 
 struct vb2_queue *vb2_queue;
 
 unsigned int num_planes;
 
/* Private: internal use only */
 enum vb2_buffer_state state;
 
 struct list_head queued_entry;
 struct list_head done_entry;
 
 struct vb2_plane planes[VIDEO_MAX_PLANES];   // 存放实际数据的结构
};
struct vb2_queue {
 enum v4l2_buf_type type;
 unsigned int io_modes;
 unsigned int io_flags;
 
 const struct vb2_ops *ops;
 const struct vb2_mem_ops *mem_ops;
 void *drv_priv;
 unsigned int buf_struct_size;
 
/* private: internal use only */
 enum v4l2_memory memory;
 struct vb2_buffer *bufs[VIDEO_MAX_FRAME];
 unsigned int num_buffers;
 
 struct list_head queued_list;
 
 atomic_t queued_count;
 struct list_head done_list;
 spinlock_t done_lock;
 wait_queue_head_t done_wq;
 
 void *alloc_ctx[VIDEO_MAX_PLANES];
 unsigned int plane_sizes[VIDEO_MAX_PLANES];
 
 unsigned int streaming:1;
 
 struct vb2_fileio_data *fileio;
};
在vb2中细化了锁,并且将核心部分封装,使我们更容易使用。
与vb中有冲突的地方如下
1,结构中红色部分都为自己的数据,一般对于我们的驱动程序来说不要使用,因此使用vb2_buffer时候不能(也不需要)直接指定vb->state 的内容。 比如说通知数据完成只需要调用 vb2_buffer_done(&vb2, VB2_BUF_STATE_DONE); 即可,
2,对于vb2_buffer,没用供我们使用的list,因此如果要将vb可以放入list head,需要我们自己添加list ,例如
struct vivi_buffer {
     /* common v4l buffer stuff -- must be first */
     struct vb2_buffer vb;
     struct list_head list;
};
使用vb2,总结如下 
    1,调用vb2_queue_init 初始化队列  q 。
    2,调用reqbuf 时候会根据请求(v4l2_requestbuffers)分配vb2结构,并且加入到q->buf中
    3,调用querybuf时候,根据信息(v4l2_buffer)返回q->buf中对应的vb2_buffer的信息(v4l2_buffer)
    4,mmap上面信息对应的 vb空间到用户空间
    5,调用qbuf 时,将对应的vb2_buffer ( vivi_bufer->list )添加到  q->queued_list 队列中
    6,使用select 调用poll 休眠等待 q->done_list 有数据
    7, 调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调如我们写的驱动中,放入一个列表,然后等待(上面的poll过程休眠)我们的驱动程序将数据放入该vb2_buffer
    8, 数据存放完成后 调用vb2_buffer_done函数,即将上面有数据的vb2_buffer放入q->done_list中,然后唤醒上面poll休眠的进程
    9, poll唤醒后会调用dqbuf将q->done_list 中的vb2_buffer提出来后,将此vb2的信息(v4l2_buffer)返回
    10, 应用程序得到buffer信息后,就去对应的mmap后的用户空间中读数据。
 
 
以上为这次根据韦东山视频步骤对vivi程序的分析,如有错误,忘指出

AudioRecord.read (buffer, 0, BUFFER_SIZE); 得到的 buffer 中的数据的单位?

AudioRecord.read (buffer, 0, BUFFER_SIZE); 得到的 buffer 中的数据的单位?

在 Android 中,使用如下方式从麦克风读取一段声音数据:

byte[] buffer = new byte[BUFFER_SIZE];
mAudioRecord.read(buffer, 0, BUFFER_SIZE);



搜索查看了很多网页,都没有具体说明这个 buffer 里最后存储的数据的单位是什么。有说是振幅,但振幅也有很多种形式,比如电压振幅、声压振幅、声压级振幅。网文中却都未有相关具体说明。
 
AudioRecord 内 read 方法实际调用的是 native 方法,所以不能进一步推断。
native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes);



希望 Oscer 们协助一下,分析分析。

Buffer的创建及使用源码分析——ByteBuffer为例

Buffer的创建及使用源码分析——ByteBuffer为例

目录

  • Buffer概述
  • Buffer的创建
  • Buffer的使用
  • 总结
  • 参考资料

Buffer概述

注:全文以ByteBuffer类为例说明
Java中提供了7种类型的Buffer,每一种类型的Buffer根据分配内存的方式不同又可以分为
直接缓冲区和非直接缓冲区。

Buffer的本质是一个定长数组,并且在创建的时候需要指明Buffer的容量(数组的长度)。
而这个数组定义在不同的Buffer当中。例如ByteBuffer的定义如下:

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    //在这里定义Buffer对应的数组,而不是在Heap-X-Buffer中定义
    //目的是为了减少访问这些纸所需的虚方法调用,但是对于小的缓冲区,代价比较高
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    //
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        //调用父类Buffer类的构造函数构造
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

    // Creates a new buffer with the given mark, position, limit, and capacity
    //
    ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }
......
}

尽管数组在这里定义,但是这个数组只对非直接缓冲区有效。

ByteBuffer类有两个子类分别是:DirectByteBuffer(直接缓冲区类)和HeapByteBuffer(非直接缓冲区)。
但是这两个类并不能直接被访问,因为这两个类是包私有的,而创建这两种缓冲区的方式就是通过调用Buffer
类提供的创建缓冲区的静态方法:allocate()allocateDirect()

Buffer的创建

Buffer要么是直接的要么是非直接的,非直接缓冲区的内存分配在JVM内存当中,
而直接缓冲区使用物理内存映射,直接在物理内存中分配缓冲区,既然分配内存的地方不一样,
BUffer的创建方式也就不一样。

非直接缓冲区内存的分配

创建非直接缓冲区可以通过调用allocate()方法,这样会将缓冲区建立在JVM内存(堆内存)当中。
allocate()方法是一个静态方法,因此可以直接使用类来调用。
具体的创建过程如下:

    /**
     * Allocates a new byte buffer.
     *
     * <p> The new buffer''s position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  It will have a {@link #array backing array},
     * and its {@link #arrayOffset array offset} will be zero.
     *
     * @param  capacity
     *         The new buffer''s capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    //分配一个缓冲区,最后返回的其实是一个HeapByteBuffer的对象
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        //这里调用到HeapByteBuffer类的构造函数,创建非直接缓冲区
        //并将需要的Buffer容量传递
        //从名称也可以看出,创建的位置在堆内存上。
        return new HeapByteBuffer(capacity, capacity);
    }

HeapByteBuffer(capacity, capacity)用于在堆内存上创建一个缓冲区。
该方法优惠调回ByteBuffer构造方法,HeapByteBuffer类没有任何的字段,他所需的字段全部定义在父类当中。
源码分析如下:

    HeapByteBuffer(int cap, int lim) {
        // 调用父类的构造方法创建非直接缓冲区           // package-private
        // 调用时根据传递的容量创建了一个数组。
        super(-1, 0, lim, cap, new byte[cap], 0);
    }
    
    //ByteBuffer类的构造方法,也就是上面代码调用的super方法
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                     byte[] hb, int offset)
        {
            //接着调用Buffer类的构造方法给用于操作数组的四个属性赋值
            super(mark, pos, lim, cap);
            //将数组赋值给ByteBuffer的hb属性,
            this.hb = hb;
            this.offset = offset;
        }

    //Buffer类的构造方法
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        //容量参数校验,原始容量不能小于0
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        //设定容量
        this.capacity = cap;
        //这里的lim从上面传递过来的时候就是数组的容量
        //limit在写模式下默认可操作的范围就是整个数组
        //limit在读模式下可以操作的范围是数组中写入的元素
        //创建的时候就是写模式,是整个数组
        limit(lim);
        //初始的position是0
        position(pos);
        //设定mark的值,初始情况下是-1,因此有一个参数校验,
        //-1是数组之外的下标,不可以使用reset方法使得postion到mark的位置。
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }

在堆上创建缓冲区还是很简单的,本质就是创建了一个数组以及一些用于辅助操作数组的其他属性。

最后返回的其实是一个HeapByteBuffer的对象,因此对其的后续操作大多应该是要调用到HeapByteBuffer类中

直接缓冲区的创建

创建直接俄缓冲区可以通过调用allocateDirect()方法创建,源码如下:

    /**
     * Allocates a new direct byte buffer.
     *
     * <p> The new buffer''s position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  Whether or not it has a
     * {@link #hasArray backing array} is unspecified.
     *
     * @param  capacity
     *         The new buffer''s capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    //创建一个直接缓冲区
    public static ByteBuffer allocateDirect(int capacity) {
        //同非直接缓冲区,都是创建的子类的对象
        //创建一个直接缓冲区对象
        return new DirectByteBuffer(capacity);
    }

DirectByteBuffer(capacity)DirectByteBuffer的构造函数,具体代码如下:

    DirectByteBuffer(int cap) {                   // package-private
        //初始化mark,position,limit,capacity
        super(-1, 0, cap, cap);
        //内存是否按页分配对齐,是的话,则实际申请的内存可能会增加达到对齐效果
        //默认关闭,可以通过-XX:+PageAlignDirectMemory控制
        boolean pa = VM.isDirectMemoryPageAligned();
        //获取每页内存的大小
        int ps = Bits.pageSize();
        //分配内存的大小,如果是按页对其的方式,需要加一页内存的容量
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        //预定内存,预定不到则进行回收堆外内存,再预定不到则进行Full gc
        Bits.reserveMemory(size, cap);
        
        long base = 0;
        try {
            //分配堆外内存
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        /**
         *创建堆外内存回收Cleanner,Cleanner对象是一个PhantomFerence幽灵引用,
         *DirectByteBuffer对象的堆内存回收了之后,幽灵引用Cleanner会通知Reference
         *对象的守护进程ReferenceHandler对其堆外内存进行回收,调用Cleanner的
         *clean方法,clean方法调用的是Deallocator对象的run方法,run方法调用的是
         *unsafe.freeMemory回收堆外内存。
        
         *堆外内存minor gc和full gc的时候都不会进行回收,而是ReferenceHandle守护进程调用
         *cleanner对象的clean方法进行回收。只不过gc 回收了DirectByteBuffer之后,gc会通知Cleanner进行回收
         */
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;

    }

由于是在物理内存中直接分配一块内存,而java并不直接操作内存需要交给JDKnative方法的实现分配

Bits.reserveMemory(size, cap)预定内存源码,预定内存,说穿了就是检查堆外内存是否足够分配

    // These methods should be called whenever direct memory is allocated or
    // freed.  They allow the user to control the amount of direct memory
    // which a process may access.  All sizes are specified in bytes.
    // 在分配或释放直接内存时应当调用这些方法,
    // 他们允许用控制进程可以访问的直接内存的数量,所有大小都以字节为单位
    static void reserveMemory(long size, int cap) {
        //memoryLimitSet的初始值为false
        //获取允许的最大堆外内存赋值给maxMemory,默认为64MB
        //可以通过-XX:MaxDirectMemorySize参数控制
        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }

        // optimist!
        //理想情况,maxMemory足够分配(有足够内存供预定)
        if (tryReserveMemory(size, cap)) {
            return;
        }

        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();

        // retry while helping enqueue pending Reference objects
        // which includes executing pending Cleaner(s) which includes
        // Cleaner(s) that free direct buffer memory
        // 这里会尝试回收堆外空间,每次回收成功尝试进行堆外空间的引用
        while (jlra.tryHandlePendingReference()) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }

        // trigger VM''s Reference processing
        // 依然分配失败尝试回收堆空间,触发full gc
        // 
        System.gc();

        // a retry loop with exponential back-off delays
        // (this gives VM some time to do it''s job)
        boolean interrupted = false;
        
        // 接下来会尝试最多9次的内存预定,应该说是9次的回收堆外内存失败的内存预定
        // 如果堆外内存回收成功,则直接尝试一次内存预定,只有回收失败才会sleep线程。
        // 每次预定的时间间隔为1ms,2ms,4ms,等2的幂递增,最多256ms。
        try {
            long sleepTime = 1;
            int sleeps = 0;
            while (true) {
                // 尝试预定内存
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                // 预定内存失败则进行尝试释放堆外内存,
                // 累计最高可以允许释放堆外内存9次,同时sleep线程,对应时间以2的指数幂递增
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }

            // no luck
            throw new OutOfMemoryError("Direct buffer memory");

        } finally {
            if (interrupted) {
                // don''t swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }

为什么调用System.gc?引用自JVM原始码分析之堆外内存完全解读

既然要调用System.gc,那肯定是想通过触发一次gc操作来回收堆外部内存,不过我想先说的是堆外部内存不会对gc造成什么影响(这里的System.gc除外),但是堆外层内存的回收实际上依赖于我们的gc机制,首先我们要知道在java尺寸和我们在堆外分配的这块内存分配的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过DirectByteBuffer对象来间接操作对应的堆外部内存了。DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference时被回收的,它不能影响gc方法,但是gc过程中如果发现某个对象只有只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref .Reference.pending物理里,在gc完成的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReferencePhantomReference的一个子类,在最终的处理里会通过Unsafe的免费接口来释放DirectByteBuffer对应的堆外内存块

Buffer的使用

切换读模式flip()

切换为读模式的代码分厂简单,就是使limit指针指向buffer中最后一个插入的元素的位置,即position,指针的位置。
position代表操作的位置,那么从0开始,所以需要将position指针归0.源码如下:

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

get()读取

get()读取的核心是缓冲区对应的数组中取出元素放在目标数组中(get(byte[] dst)方法是有一个参数的,传入的就是目标数组)。

    public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
    }

    public ByteBuffer get(byte[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
            throw new BufferUnderflowException();
        int end = offset + length;
        //shiyongfor循环依次放入目标数组中
        for (int i = offset; i < end; i++)
            // get()对于直接缓冲区和非直接缓冲区是不一样的,所以交由子类实现。
            dst[i] = get();
        return this;
    }

rewind()重复读

既然要重复读就需要把position置0了

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

clear()清空缓冲区与compact()方法

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

clear()方法中,仅仅是将三个指针还原为创建时的状态供后续写入,但是之前写入的数据并没有被删除,依然可以使用get(int index)获取

但是有一种情况,缓冲区已经满了还想接着写入,但是没有读取完又不能从头开始写入该怎么办,答案是compact()方法

非直接缓冲区:
   public ByteBuffer compact() {
        //将未读取的部分拷贝到缓冲区的最前方
        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
        //设置position位置到缓冲区下一个可以写入的位置
        position(remaining());
        //设置limit是最大容量
        limit(capacity());
        //设置mark=-1
        discardMark();
        return this;
    }

直接缓冲区:
    public ByteBuffer compact() {    
        int pos = position();
        int lim = limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        //调用native方法拷贝未读物部分
        unsafe.copyMemory(ix(pos), ix(0), (long)rem << 0);
        //设定指针位置
        position(rem);
        limit(capacity());
        discardMark();
        return this;
    }

mark()标记位置以及reset()还原

mark()标记一个位置,准确的说是当前的position位置

    public final Buffer mark() {
        mark = position;
        return this;
    }

标记了之后并不影响写入或者读取,position指针从这个位置离开再次想从这个位置读取或者写入时,
可以使用reset()方法

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

总结

本文其实还有很多不清楚的地方,对于虚引用以及引用队列的操作还不是很清楚去,对于虚引用和堆外内存的回收的关系源码其实也没看到,
需要再看吧,写这篇的目的其实最开始就是想研究看看直接缓冲区内存的分配,没想到依然糊涂,后面填坑。路过的大佬也就指导下虚引用这部分相关的东西,谢谢。

参考资料

  • JVM原始码分析之堆外内存完全解读
  • Java Nio 之直接内存
  • Java直接内存分配与释放原理

今天的关于JDK ByteBuffer & MINA IoBuffer 总结&比较bytebuffer java的分享已经结束,谢谢您的关注,如果想了解更多关于.net – 在哪里IBuffer byte [] .AsBuffer的扩展方法?、24、vb2_buffer和videobuf_buffer比较分析、AudioRecord.read (buffer, 0, BUFFER_SIZE); 得到的 buffer 中的数据的单位?、Buffer的创建及使用源码分析——ByteBuffer为例的相关知识,请在本站进行查询。

本文标签: