Segment
官方解釋
- Segment 是 buffer 切割后的組成部分.
- 每個 buffer 中的 Segment 都是循環(huán)鏈表中的節(jié)點(diǎn),持有上一個 Segment 和下一個 Segment 的引用.
- 每個緩存池中的 Segment 都是單鏈表中的節(jié)點(diǎn).
- Segment 底層數(shù)組可以被 buffer 和字符串共享.當(dāng)一個 Segment 是被共享狀態(tài)時不可以被回收,其字節(jié)數(shù)據(jù)只能讀不可寫.
- 唯一例外的是當(dāng) owner 為 true 時,數(shù)據(jù)區(qū)間為 [limit,SIZE) 中可以做寫操作.
- 每個數(shù)組對應(yīng)一個 Segment 作為持有者.
- Positions, limits, prev, next 不可以被共享.
成員變量
// Segment.java
// segment 數(shù)據(jù)字節(jié)數(shù)最大值為 8kb
static final int SIZE = 8192;
/* SHARE_MINIMUM 是調(diào)用 split() 時根據(jù)操作字節(jié)大小(byteCount)判斷使用共享 segment 實(shí)現(xiàn)還是
使用數(shù)據(jù)復(fù)制實(shí)現(xiàn)的標(biāo)準(zhǔn) */
static final int SHARE_MINIMUM = 1024;
// 存放數(shù)據(jù)的字節(jié)數(shù)組
final byte[] data;
// 下一個可讀字節(jié)的下標(biāo)
int pos;
// 第一個可寫字節(jié)的下標(biāo),即最后一個可讀數(shù)據(jù)下標(biāo)為 limit-1
int limit;
// 是否與其他 segment 持有同一個數(shù)組 data 對象
boolean shared;
// 是否為 data 的所有者
// true 對 data 有讀寫的權(quán)限,可寫的范圍與 shared 有關(guān)
// false 只讀
boolean owner;
// 下一個 segment
Segment next;
// 上一個 segment
Segment prev;
- 字節(jié)數(shù)組中有兩個特殊區(qū)域,分別是已讀區(qū)域 [pos, limit-1] 和可寫區(qū)域 [limit, SIZE)
- shared owner 共同作用限制了 Segment 的讀寫權(quán)限以及可寫的范圍,下文閱讀 writeTo() 會介紹他們的作用.
構(gòu)造方法
// Segment.java
Segment() {
this.data = new byte[SIZE];
this.owner = true;
this.shared = false;
}
Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) {
this.data = data;
this.pos = pos;
this.limit = limit;
this.shared = shared;
this.owner = owner;
}
無參構(gòu)造方法構(gòu)造默認(rèn)的 Segment ,data 大小是默認(rèn) 8k, onwer 為 true, shared 為 false,此時 Segment 沒有數(shù)據(jù).
有參構(gòu)造可以構(gòu)造自定義的 Segment,并設(shè)置 data pos limit shared owner 的值.
sharedCopy 和 unsharedCopy
// Segment.java
/**
* 返回一個新的 segment 并與當(dāng)前 segment 使用同一個 data 引用,相當(dāng)于淺克隆
*/
Segment sharedCopy() {
// 設(shè)置當(dāng)前 segment 的 shared 為 true 可以防止 segment 被回收
shared = true;
return new Segment(data, pos, limit, true, false);
}
/**
* 返回一個新的 segment , data 是 segment.data 深克隆得到的對象
*/
Segment unsharedCopy() {
return new Segment(data.clone(), pos, limit, false, true);
}
兩個方法都是復(fù)制當(dāng)前 Segment 且當(dāng)前 Segment 和復(fù)制品的 pos 和 limit 值相同,兩個方法區(qū)別:
- sharedCopy() 被調(diào)用時,當(dāng)前 Segment 和復(fù)制得到的 Segment 會持有相同的數(shù)組對象 data ,所以兩個 Segment 的 shared 都要設(shè)置為 true 且復(fù)制的 Segment 的 owner 值為 false.
- unsharedCopy() 被調(diào)用時,復(fù)制的 Segment 持有的是當(dāng)前 Segment 數(shù)組 data 的深克隆得到的對象,所以他是克隆對象 data 的持有者, owner 為 true 且 shared 為 false.
由此可見只有最初持有 data 的 Segment 是數(shù)組的持有者 owner 為 true,其他調(diào)用 sharedCopy 復(fù)制的 Segment 都為 false.
push
// Segment.java
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
在當(dāng)前 Segment 與上一個節(jié)點(diǎn)之間插入一個 Segment 并返回被插入的 Segment.
pop
public @Nullable Segment pop() {
/* 如果當(dāng)前 segment 下一個節(jié)點(diǎn)就是指向它自己,那么鏈表只有一個 segment,result 為 null,
且下面兩行代碼的執(zhí)行毫無意義. */
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}
把當(dāng)前 Segment 從循環(huán)鏈表中移除并返回,如果當(dāng)前 Segment 本身就不在循環(huán)鏈表內(nèi)就返回 null.
writeTo
// Segment.java
public void writeTo(Segment sink, int byteCount) {
// 1
if (!sink.owner) throw new IllegalArgumentException();
// 2
if (sink.limit + byteCount > SIZE) {
// 2.1
if (sink.shared) throw new IllegalArgumentException();
// 2.2
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;
}
讀取當(dāng)前 Segment 數(shù)據(jù)并寫入目標(biāo) Segment (sink) 中.
- 如果 sink.owner 為 false 即 sink 的數(shù)據(jù)只讀不寫,直接拋異常.
當(dāng)前數(shù)組 data 可以分為三個部分 [0, pos) [pos, limit) [limit, SIZE) ,第一個區(qū)域在非共享狀態(tài)下是可回收區(qū)域,第二第三個就是之前說的可讀和可寫區(qū)域.
- 首先判斷 sink 的可寫區(qū)域是否足夠:
- true 如果足夠直接從 sink 數(shù)組下標(biāo) limit 開始寫入數(shù)據(jù).
- false 如果不足夠可以通過移動數(shù)組中的可讀數(shù)據(jù)到 [0,limit-pos) ,把第一個區(qū)域的空間回收到可寫區(qū)域中,但實(shí)現(xiàn)該方法需要考慮 sink.shared 的值:
- true sink 是共享狀態(tài)(2.1)數(shù)據(jù)是不可以移動的只能拋異常.
- false sink 不是共享狀態(tài)數(shù)組數(shù)據(jù)可移動,但移動之前還需計算判斷移動后的可寫區(qū)域大小是否足夠(2.2):
- false 移動之后還是不夠空間,拋異常.
- true 移動后有足夠空間,通過數(shù)組復(fù)制方式往 sink 中寫入數(shù)據(jù).
數(shù)據(jù)通過調(diào)用 System.arraycopy 寫進(jìn)目標(biāo) Segment 后還需要設(shè)置當(dāng)前 pos 和目標(biāo) Segment 的 limit.
該方法還表明了 owner 與 shared 之間的聯(lián)系:
- owner = true && shared = true : 數(shù)組 [0, limit-1] 范圍內(nèi)只讀不可寫,可寫范圍 [limit, SIZE] ,寫大小為 SIZE - limit.
- owner = true && shared = false : 整個數(shù)組 data 都是可讀可寫的,可以把可讀數(shù)據(jù)在數(shù)組中隨意移動,可寫大小為 SIZE - (limit - pos).
- owner = false 時 shared 只能是 true, Segment 持有的數(shù)據(jù)不可以做任何修改,只讀不寫.
split
// Segment.java
public Segment split(int byteCount) {
// byteCount 不可以 <= 0 || byteCount 必須大于有效數(shù)據(jù)的大小,不然沒必要拆分
if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
Segment prefix;
// 兩個性能指標(biāo):
// - 避免復(fù)制操作.可以使用共享 segment 達(dá)到目的.
// - 避免共享數(shù)據(jù)量小的 segment,這樣會在鏈表中出現(xiàn)一串?dāng)?shù)據(jù)量小的 segment 且他們都是只讀,會影響性能.
// 為了得到平衡,只會在復(fù)制操作代價足夠大的時候才使用共享 segment
if (byteCount >= SHARE_MINIMUM) {
prefix = sharedCopy();
} else {
prefix = SegmentPool.take();
System.arraycopy(data, pos, prefix.data, 0, byteCount);
}
prefix.limit = prefix.pos + byteCount;
pos += byteCount;
prev.push(prefix);
return prefix;
}
split() 以 byteCount 為數(shù)據(jù)大小分界線,把當(dāng)前 Segment 一分為二.
從數(shù)據(jù)內(nèi)容角度看是把下標(biāo)區(qū)間 [pos, limit) 分為 [pos, pos+byteCount) 和 [pos+byteCount, limit).
分割的實(shí)現(xiàn)有兩種方法:
- 調(diào)用 sharedCopy 構(gòu)建新的 Segment 且兩個 Segment 共享同一個數(shù)組 data 對象,然后設(shè)置兩個 Segment 的 pos 和 limit.
- 從緩存池中獲取 Segment 并用 System.arraycopy 復(fù)制數(shù)據(jù),然后設(shè)置兩個 Segment 的 pos 和 limit.
優(yōu)點(diǎn):
- share 方案可以減少 System.arraycopy 的調(diào)用提高了性能,共享 Segment 持有同一個數(shù)組 data 對象減少內(nèi)存消耗.
缺點(diǎn):
- share 方案中的 Segment 且非數(shù)組持有者都只是可讀不可寫的,即使是數(shù)組持有者可寫范圍也受到限制,當(dāng)循環(huán)鏈表中存在大量共享狀態(tài)且數(shù)據(jù)量小的 Segment 的時候,這些 Segment 對象會占用過多內(nèi)存資源.
- 數(shù)組復(fù)制方案涉及到底層方法,占用 CPU 資源,操作的字節(jié)數(shù)越大時性能損耗越明顯.
所以 Segment 規(guī)定當(dāng)操作數(shù)據(jù)大小小于 1k 時用數(shù)據(jù)復(fù)制方案,超過 1k 用共享方案.
compact
// Segment.java
public void compact() {
if (prev == this) throw new IllegalStateException();
// 當(dāng) prev 是只讀的時候不可以合并
if (!prev.owner) return;
// 操作字節(jié)數(shù)就是當(dāng)前 segment 的數(shù)據(jù)大小
int byteCount = limit - pos;
// 根據(jù)是否共享狀態(tài)計算 prev 的可寫范圍大小:
// true 共享狀態(tài)可寫范圍是 [limit, SIZE),
// false 非共享可寫范圍是 [0, pos) + [limit, SIZE)
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
// 如果 prev 可寫大小小于當(dāng)前 segment 數(shù)據(jù)大小,就不可以合并
if (byteCount > availableByteCount) return;
// 把當(dāng)前 segment 數(shù)據(jù)寫進(jìn) prev 中
writeTo(prev, byteCount);
// 把當(dāng)前 segment 從循環(huán)鏈表中移除
pop();
// 回收當(dāng)前 segment
SegmentPool.recycle(this);
}
條件允許情況下合并當(dāng)前 Segment 數(shù)據(jù)到上一個 Segment 中,可以減少循環(huán)鏈表的節(jié)點(diǎn)數(shù)且盡可能地保證所有節(jié)點(diǎn)的數(shù)據(jù)占用率在 50% 以上.
- 當(dāng)前 Segment 的上一個 Segment 最大可寫大小 >= 當(dāng)前 Segment 數(shù)據(jù)大小的時候,合并這兩個 Segment 中的數(shù)據(jù)到上一個 Segment 中并把當(dāng)前 Segment 從循環(huán)鏈表中移除然后添加到緩存池.
- 通常是由鏈表中的尾結(jié)點(diǎn) tail 調(diào)用該方法.
SegmentPool
// SegmentPool.java
/**
* 無用 Segment 的集合,防止被 GC 和零填充
* 緩存池是靜態(tài)單例的保證了線程安全
*/
final class SegmentPool {
// 緩存池最大容量 64kb
static final long MAX_SIZE = 64 * 1024;
// 指向單鏈表中下一個節(jié)點(diǎn),就是單鏈表的頭結(jié)點(diǎn)
static @Nullable Segment next;
// 記錄緩存池中所有 Segment 數(shù)據(jù)大小之和
static long byteCount;
private SegmentPool() {
}
// 從緩存池中獲取一個 Segment
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {
Segment result = next;
next = result.next;
result.next = null;
// 減去一個 Segment 容量
byteCount -= Segment.SIZE;
return result;
}
}
return new Segment();
}
// 回收一個 Segment
static void recycle(Segment segment) {
// segment 必須不存在上一個節(jié)點(diǎn)和下一個節(jié)點(diǎn)的引用,否則報錯
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
// 如果該 segment 是被共享狀態(tài),不可以被回收
if (segment.shared) return;
synchronized (SegmentPool.class) {
// 緩存池已滿,return
if (byteCount + Segment.SIZE > MAX_SIZE) return;
// byteCount 添加一個 segment 的容量
byteCount += Segment.SIZE;
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
}
- SegmentPool 通過一個單鏈表實(shí)現(xiàn),緩存最大容量是 64kb 個(有點(diǎn)多).
- SegmentPool 不會回收共享狀態(tài)的 Segment.
- SegmentPool 只回收指向上一個節(jié)點(diǎn)和下一個節(jié)點(diǎn)都為 null 的 Segment.
- SegmentPool 中獲取的 Segment 可能保留著上次使用時的數(shù)據(jù).