首页 > 其他分享 >startFromBuffer

startFromBuffer

时间:2024-09-11 19:13:30浏览次数:1  
标签:Cleaner return startFromBuffer int 缓冲区 IntBuffer public

1.Buffer类的底层实现

以IntBuffer和HeapIntBuffer为例讲解Buffer的实现机制

  1. 核心内容

    public abstract class Buffer {
        // 这四个变量的关系: mark <= position <= limit <= capacity
      	// 这些变量就是Buffer操作的核心了,之后我们学习的过程中可以看源码是如何操作这些变量的
        private int mark = -1;
        private int position = 0;
        private int limit;
        private int capacity;
    
        // 直接缓冲区实现子类的数据内存地址
        long address;
        ...
    }
    

    成员解读:

    • mark: 用于position回溯的一个标记,如果标记小于0的时候reset会抛出异常
    • position: 用于记录写入或读取的位置的索引
    • limit: 缓冲区的一个读取限制 初始和capacity相同 但是如果执行'压缩'之后limit可能变化
    • capacity: 缓冲的容量 或 底层数组的长度 真实的尺寸

    Buffer类的子类,包括我们认识到的所有基本类型(除了boolean类型之外):

    • IntBuffer - int类型的缓冲区。
    • ShortBuffer - short类型的缓冲区。
    • LongBuffer - long类型的缓冲区。
    • FloatBuffer - float类型的缓冲区。
    • DoubleBuffer - double类型的缓冲区。
    • ByteBuffer - byte类型的缓冲区。
    • CharBuffer - char类型的缓冲区。

    (注意我们之前在JavaSE中学习过的StringBuffer虽然也是这种命名方式,但是不属于Buffer体系)

  2. 使用关键

    • 读取和写入时会造成position的改动,比如写入数据后,如果需要从头读,需要flip进行重置

    • 复制、切分时底层使用的同一个数组,也就是操作实际上是对同一个数组进行操作

2.常用api

  • public abstract IntBuffer put(int i); - 在当前position位置插入数据,由具体子类实现

  • public abstract IntBuffer put(int index, int i); - 在指定位置存放数据,也是由具体子类实现

  • public final IntBuffer put(int[] src); - 直接存放所有数组中的内容(数组长度不能超出缓冲区大小)

  • public IntBuffer put(int[] src, int offset, int length); - 直接存放数组中的内容,同上,但是可以指定存放一段范围

  • public IntBuffer put(IntBuffer src); - 直接存放另一个缓冲区中的内容

  • public abstract int get(); - 直接获取当前position位置的数据,由子类实现

  • public abstract int get(int index); - 获取指定位置的数据,也是子类实现

  • public IntBuffer get(int[] dst); - 将数据读取到给定的数组中

  • public IntBuffer get(int[] dst, int offset, int length); - 同上,加了个范围

  • public int[] array(); - 直接返回底层存储的数组

  • public final Buffer mark() - 标记当前位置

  • public final Buffer reset() - 让当前的position位置跳转到mark当时标记的位置

  • public abstract IntBuffer compact() - 压缩缓冲区,由具体实现类实现

    • 源码

    • public IntBuffer compact() {
          int pos = position();   //获取当前位置
          int lim = limit();    //获取当前最大position位置
          assert (pos <= lim);   //断言表达式,position必须小于最大位置,肯定的
          int rem = (pos <= lim ? lim - pos : 0);  //计算pos距离最大位置的长度
          System.arraycopy(hb, ix(pos), hb, ix(0), rem);   //直接将hb数组当前position位置的数据拷贝到头部去,然后长度改成刚刚计算出来的空间
          position(rem);   //直接将position移动到rem位置
          limit(capacity());   //pos最大位置修改为最大容量
          discardMark();   //mark变回-1
          return this;
      }
      
  • public IntBuffer duplicate() - 复制缓冲区,会直接创建一个新的数据相同的缓冲区

  • public abstract IntBuffer slice() - 划分缓冲区,会将原本的容量大小的缓冲区划分为更小的出来进行操作

  • public final Buffer rewind() - 重绕缓冲区,其实就是把position归零,然后mark变回-1

  • public final Buffer clear() - 将缓冲区清空,所有的变量变回最初的状态

  • public boolean equals(Object ob) - 比较剩余的内容

    • public boolean equals(Object ob) {
          if (this == ob)   //要是两个缓冲区是同一个对象,肯定一样
              return true;
          if (!(ob instanceof IntBuffer))  //类型不是IntBuffer那也不用比了
              return false;
          IntBuffer that = (IntBuffer)ob;   //转换为IntBuffer
          int thisPos = this.position();  //获取当前缓冲区的相关信息
          int thisLim = this.limit();
          int thatPos = that.position();  //获取另一个缓冲区的相关信息
          int thatLim = that.limit();
          int thisRem = thisLim - thisPos; 
          int thatRem = thatLim - thatPos;
          if (thisRem < 0 || thisRem != thatRem)   //如果剩余容量小于0或是两个缓冲区的剩余容量不一样,也不行
              return false;
        	//注意比较的是剩余的内容
          for (int i = thisLim - 1, j = thatLim - 1; i >= thisPos; i--, j--)  //从最后一个开始倒着往回比剩余的区域
              if (!equals(this.get(i), that.get(j)))
                  return false;   //只要发现不一样的就不用继续了,直接false
          return true;   //上面的比较都没问题,那么就true
      }
      
      private static boolean equals(int x, int y) {
          return x == y;
      }
      
  • public int compareTo(IntBuffer that) - 比较剩余的内容

    • public int compareTo(IntBuffer that) {
          int thisPos = this.position();    //获取并计算两个缓冲区的pos和remain
          int thisRem = this.limit() - thisPos;
          int thatPos = that.position();
          int thatRem = that.limit() - thatPos;
          int length = Math.min(thisRem, thatRem);   //选取一个剩余空间最小的出来
          if (length < 0)   //如果最小的小于0,那就返回-1
              return -1;
          int n = thisPos + Math.min(thisRem, thatRem);  //计算n的值当前的pos加上剩余的最小空间
          for (int i = thisPos, j = thatPos; i < n; i++, j++) {  //从两个缓冲区的当前位置开始,一直到n结束
              int cmp = compare(this.get(i), that.get(j));  //比较
              if (cmp != 0)
                  return cmp;   //只要出现不相同的,那么就返回比较出来的值
          }
          return thisRem - thatRem; //如果没比出来个所以然,那么就比长度
      }
      
      private static int compare(int x, int y) {
          return Integer.compare(x, y);
      }
      

3.只读缓冲

  • 创建只读缓冲

    • public abstract IntBuffer asReadOnlyBuffer(); - 基于当前缓冲区生成一个只读的缓冲区。

    • public IntBuffer asReadOnlyBuffer() {
          return new HeapIntBufferR(hb,    //注意这里并不是直接创建了HeapIntBuffer,而是HeapIntBufferR,并且直接复制的hb数组
                                       this.markValue(),
                                       this.position(),
                                       this.limit(),
                                       this.capacity(),
                                       offset);
      }
      
    • protected HeapIntBufferR(int[] buf,
                                     int mark, int pos, int lim, int cap,
                                     int off)
      {
          super(buf, mark, pos, lim, cap, off);
          this.isReadOnly = true;
      }
      
  • 写操作全部抛出异常 禁止进行写操作

4.ByteBuffer与CharBuffer

4.1 ByteBuffer

实现

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers
  	....

4.2 CharBuffer

​ 由于使用了char[] 存储数据,此缓冲可以使用字符串相关的api如append、charAt等

5.直接缓冲区

  • 堆缓冲区的数据实际上保存在堆内存中,我们可以创建一个直接缓冲区,申请堆外内存进行数据保存,采用操作系统本地的io,相比堆缓冲区会快一些

  • 在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在物理内存内,会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销。

  • 创建:allocateDirect(capacity)

  • 知识补充:

    1. Unsafe是位于sun.misc包下的类,提供一些更底层的,访问系统内存资源,和管理系统内存资源的方法,但是因为会访问系统的内存资源 变成和C语言一样的指针,指针的使用是有风险的,所以Unsafe也是有类似的风险,所以在使用的时候需要注意,过度或者不正确的使用可能导致程序出错。
      但是,Unsafe类也使得Java增强了底层操作系统资源的能力。
      同时,Unsafe提供的功能的实现是依赖于本地方法(Native Method)的,本地方法就是Java中使用其他语言写的方法,本地方法用native修饰,java只声明方法,具体实现由本地方法实现。
    2. 虚引用PhantomReference,虚引用是最弱的一种java对象引用方式,其他的引用方式至少还能get到对象,而虚引用的句柄是获取不到对象的,正如它的名字一样:形同虚设。虚引用的作用就是在对象被GC回收时能得到通知。如何通知呢?就是在对象被回收后,把它的弱引用对象(PhantomReference)存入QUEUE对列中,这样我们查看队列就可以得知某个对象被GC回收了
  • 构造

    DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();   //是否直接内存分页对齐,需要额外计算
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));   //计算出最终需要申请的大小
      	//判断堆外内存是否足够,够的话就作为保留内存
        Bits.reserveMemory(size, cap);
    
        long base = 0;
        try {
          	//通过Unsafe申请内存空间,并得到内存地址
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
          	//申请失败就取消一开始的保留内存
            Bits.unreserveMemory(size, cap);
            throw x;
        }
      	//批量将申请到的这一段内存每个字节都设定为0
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
          	//将address变量(在Buffer中定义)设定为base的地址
            address = base;
        }
      	//创建一个针对于此缓冲区的Cleaner,由于是堆外内存,所以现在由它来进行内存清理
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }
    
  • 清理-守护线程定时检查(或者也可以手动调用cleaner方法显示的回收)

    public class Cleaner extends PhantomReference<Object>{ //继承自鬼引用,也就是说此对象会存放一个没有任何引用的对象
    
        //引用队列,PhantomReference构造方法需要
        private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
      	
      	//执行清理的具体流程
        private final Runnable thunk;
      
      	static private Cleaner first = null;  //Cleaner双向链表,每创建一个Cleaner对象都会添加一个结点
    
        private Cleaner
            next = null,
            prev = null;
      
      	private static synchronized Cleaner add(Cleaner cl) {   //添加操作会让新来的变成新的头结点
            if (first != null) {
                cl.next = first;
                first.prev = cl;
            }
            first = cl;
            return cl;
        }
    
      	//可以看到创建鬼引用的对象就是传进的缓冲区对象
        private Cleaner(Object referent, Runnable thunk) {
            super(referent, dummyQueue);
          	//清理流程实际上是外面的Deallocator
            this.thunk = thunk;
        }
    
       	//通过此方法创建一个新的Cleaner
        public static Cleaner create(Object ob, Runnable thunk) {
            if (thunk == null)
                return null;
            return add(new Cleaner(ob, thunk));   //调用add方法将Cleaner添加到队列
        }
      
      	//清理操作
      	public void clean() {
            if (!remove(this))
                return;    //进行清理操作时会从双向队列中移除当前Cleaner,false说明已经移除过了,直接return
            try {
                thunk.run();   //这里就是直接执行具体清理流程
            } catch (final Throwable x) {
                ...
            }
        }
    

    具体的清理操作

    private static class Deallocator implements Runnable {
    
        private static Unsafe unsafe = Unsafe.getUnsafe();
    
        private long address;   //内存地址
        private long size;    //大小
        private int capacity;   //申请的容量
    
        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }
    
        public void run() {   //具体的清理操作
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);   //这里是直接调用了Unsafe进行内存释放操作
            address = 0;   //内存地址改为0,NULL
            Bits.unreserveMemory(size, capacity);   //取消一开始的保留内存
        }
    }
    
  • 清理时机:和堆缓冲区一样,当直接缓冲区没有任何强引用时,就有机会被GC正常回收掉并自动释放申请的内存。

    Reference Handler线程是在一开始就启动了

    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {   //加锁办事
              	//当Cleaner引用的DirectByteBuffer对象即将被回收时,pending会变成此Cleaner对象
              	//这里判断到pending不为null时就需要处理一下对象销毁了
                if (pending != null) {
                    r = pending;
                    // 'instanceof' 有时会导致内存溢出,所以将r从链表中移除之前就进行类型判断
                    // 如果是Cleaner类型就给到c
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // 将pending更新为链表下一个待回收元素
                    pending = r.discovered;
                    r.discovered = null;   //r不再引用下一个节点
                } else {
                  	//否则就进入等待
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
    
        // 如果元素是Cleaner类型,c在上面就会被赋值,这里就会执行其clean方法(破案了)
        if (c != null) {
            c.clean();
            return true;
        }
    
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);  //这个是引用队列,实际上就是我们之前在JVM篇中讲解的入队机制
        return true;
    }
    

标签:Cleaner,return,startFromBuffer,int,缓冲区,IntBuffer,public
From: https://www.cnblogs.com/yuqiu2004/p/18408781

相关文章