OKIO源码分析<Segment的设计智慧>

1.Segment.java

a).final class Segment,只看类的修饰符表明这个类不能被继承
b).看看成员变量:

 /*当前segement所占的字节数*/
  static final int SIZE = 8192;
 /*当前segment被分享的最小字节数,也就是当需要达到共享的数据的大小比1024字节数大的时候才共享*/
  static final int SHARE_MINIMUM = 1024;
  //当前segment存储的数据:data数组
  final byte[] data;
  /** The next byte of application data byte to read in this segment. */
  /*segment中有效数据的开始index*/
  int pos;
  /** The first byte of available data ready to be written to. */
  /*segment新数据可写入的index,也是有效数据的结束后一位,也就是segment中的数据index是从pos到limit-1*/
  int limit;
  /** True if other segments or byte strings use the same byte array. */
  /*是否与其他的segment或者byte共享标志位*/
  boolean shared;
  /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
  /*此segment数据是否为独享数据*/
  boolean owner;
  /** Next segment in a linked or circularly-linked list. */
  /*当前节点的后继节点*/
  Segment next;

  /** Previous segment in a circularly-linked list. */
  /*当前节点的先驱节点*/
  Segment prev;

从源码可以看出这是一个双向链表:
这里写图片描述
链表中的每一个结点都有一个data数组:data = new byte[SIZE];
boolean shared:标志位表示data数组是否为其他segment对象共享;
boolean owner:标志data数组是否只能由当前对象操作;
Segment之间使用链表连接,Segment中使用数组存储,兼具读的连续性和写的可插入性。

c).构造函数

  /*构造函数*/
  Segment() {
    this.data = new byte[SIZE];
    this.owner = true;
    this.shared = false;
  }
  /*构造函数*/
  Segment(Segment shareFrom) {
    this(shareFrom.data, shareFrom.pos, shareFrom.limit);
    shareFrom.shared = true;
  }
/*构造函数*/
  Segment(byte[] data, int pos, int limit) {
    this.data = data;
    this.pos = pos;
    this.limit = limit;
    this.owner = false;
    this.shared = true;
  }

d).pop()

  /**
   * Removes this segment of a circularly-linked list and returns its .
   * Returns null if the list is now empty.
   */
   /*
  移除当前节点,将先前节点的后继指针指向当前节点的后继节点,下一节点前驱指向     当前节点的前驱  然后再将当前节点的前驱和后继节点置空,当前节点脱离双向链表
   */
  public Segment pop() {
    Segment result = next != this ? next : null;
    prev.next = next;
    next.prev = prev;
    next = null;
    prev = null;
    return result;
  }

函数作用示意图:

这里写图片描述
e).push()

/**
   * Appends {@code segment} after this segment in the circularly-linked list.
   * Returns the pushed segment.
   */
   /*
  在当前节点的后继位置插入新结点,插入节点的前驱指向当前节点,插入节点的后继节点指向当前节点的后继节点,返回插入的节点
   */
  public Segment push(Segment segment) {
    segment.prev = this;
    segment.next = next;
    next.prev = segment;
    next = segment;
    return segment;
  }

f.split()

  /**
   * Splits this head of a circularly-linked list into two segments. The first
   * segment contains the data in {@code [pos..pos+byteCount)}. The second
   * segment contains the data in {@code [pos+byteCount..limit)}. This can be
   * useful when moving partial segments from one buffer to another.
   *
   * <p>Returns the new head of the circularly-linked list.
   */

   /*
    切割当前segment,前一个的segment的data为当前segment的pos..pos+byteCount字节片段
    当前segment数据段变为pos+byteCount..limit字节片段,
    这可以用来从一个buffer移动部分数据到另外一个buffer
    主要为了实现在移动数据时直接操作Segment而不是data,这样在写数据时可以达到很高的效率。
   */
  public Segment split(int byteCount) {
    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
    Segment prefix;

    // We have two competing performance goals:
    //  - Avoid copying data. We accomplish this by sharing segments.
    //  - Avoid short shared segments. These are bad for performance because they are readonly and
    //    may lead to long chains of short segments.
    // To balance these goals we only share segments when the copy will be large.

    /*如果bytecount比最小可共享上限大,
    则不复制这个data数组,两个segment共享这个segment的一部分
    否则就拷贝数据,以达到频繁拷贝数组;同时也要兼顾双向链表的长度,如果共享片段链很长,
    但是链中的每一个segment都很短的话,就会使链表很长,所以在是否拷贝data数组很重要
    */
    if (byteCount >= SHARE_MINIMUM) {
      //共享
      prefix = new Segment(this);
    } else {
      //不共享,拷贝
      prefix = SegmentPool.take();
      System.arraycopy(data, pos, prefix.data, 0, byteCount);
    }

    prefix.limit = prefix.pos + byteCount;
    pos += byteCount;
    prev.push(prefix);
    return prefix;
  }

如果两个segment对象共享一个data数组,则两个segmentdata示意图:
这里写图片描述

并且将共享为修改为TRUE,以防止SegmentPool的回收
g).compact()
/**
* Call this when the tail and its predecessor may both be less than half
* full. This will copy data so that segments can be recycled.
*/

/*
当segment尾部和让的前驱结点中的数据都不足当前segment的一般的时候调用,
会拷贝数据以至于segment的回收
*/
public void compact() {
//当前只有一个节点的时候
if (prev == this) throw new IllegalStateException();
//不能压缩因为当前数据被共享了
if (!prev.owner) return; // Cannot compact: prev isn’t writable.
int byteCount = limit - pos;
//当前节点的前驱segment是否被共享,如果被共享则再加上pos之前的数据(因为之前的是无效数据),共享了则不将pos之前的位置加入可用空间
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
//没有足够的空间,放弃压缩
if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
//将当前节点的数据写入前驱结点
writeTo(prev, byteCount);
//从双向链表中删除当前节点,并且让segmentpool回收当前的segment
pop();
SegmentPool.recycle(this);
}
h).writeTo


  /** Moves {@code byteCount} bytes from this segment to {@code sink}. */
  public void writeTo(Segment sink, int byteCount) {
    //当前节点为独享
    if (!sink.owner) throw new IllegalArgumentException();
    if (sink.limit + byteCount > SIZE) {
      //将数据往前移动
      // We can't fit byteCount bytes at the sink's current position. Shift sink first.
      if (sink.shared) throw new IllegalArgumentException();
      if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
      System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
      sink.limit -= sink.pos;
      sink.pos = 0;
    }

    System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
    sink.limit += byteCount;
    pos += byteCount;
  }
}

2.segmentPool.java

final class SegmentPool {
  /** The maximum number of bytes to pool. */
  /*
  Segment池所能维护的最大的字节数
  */
  // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
  static final long MAX_SIZE = 64 * 1024; // 64 KiB.

//segment池中链表的头节点
  /** Singly-linked list of segments. */
  static Segment next;
//当前segment池的字节数
  /** Total bytes in this pool. */
  static long byteCount;
//构造函数
  private SegmentPool() {
  }
//从segment池中获取一个segment
  static Segment take() {
    //加锁将池中当前的链表头返回
    synchronized (SegmentPool.class) {
      if (next != null) {
        Segment result = next;
        next = result.next;
        result.next = null;
        byteCount -= Segment.SIZE;
        return result;
      }
    }
    //如果池维护的链表为空就重新实例化一个segment
    return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
  }
//回收segment,将所回收的segment添加到池维护的链表头
  static void recycle(Segment segment) {
    if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
    if (segment.shared) return; // This segment cannot be recycled.
    synchronized (SegmentPool.class) {
      if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
      byteCount += Segment.SIZE;
      segment.next = next;
      segment.pos = segment.limit = 0;
      next = segment;
    }
  }
}

如何回收?
看看当前SegmentPool池中的维护的链表:

这里写图片描述

首先判断void recycle(Segment segment)的前驱后继指针是否为null,非null抛出异常,不能回收;
如果当前Segment的data数组还被其他的Segment对象共享,回收失败;
如果当前节点均满足不满足上述条件开始真正的回收,将当前对象加入当前SegmentPool池链表表头,以备需要之时通过take()函数取出。

每一次通过Segment take()函数取出一个可用的Segment对象的时候会从这个链表表头取出,然后将当前表头的下一Segment对象作为链表的表头。如果链表为空的话就重新实例化一个Segment对象。

3.池的智慧

okio采用了segment的机制进行内存共享和复用,尽可能少的去申请内存,同时也就降低了GC的频率。我们知道,过于频繁的GC会给应用程序带来性能问题。
可以看到这里有一个SegmentPool和线程池的设计思想相同,因为每一次实例化Segment对象开销都会比较大,如果能在Segment对象已经不被使用的时候将它“存起来”,等到需要之时才取出来,这样就比重新申请一个内存来实例化一个Segment对象的开销要小,而且快。但是这个池并不是回收所有的已经被废弃的Segment对象,它只回收一定数量的Segment对象,从代码中可以看出池中所能保存的Segment对象总大小只有
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
超过这个最大值的时候,就不再回收,交由JVM的GC回收。否则Segment池保存太多的Segment对象会浪费内存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值