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对象会浪费内存。