如果请求的内存大于BufferPool中总共可用的内存,就需要额外增加内存,通过Deque的方法传入内存。free list的内存= free.size() * poolableSize, free list 的内存 + nonPooledAvailableMemory = 总共可用的内存。
先来看下它的属性:
static final String WAIT_TIME_SENSOR_NAME = "bufferpool-wait-time";
private final long totalMemory;
private final int poolableSize;
private final ReentrantLock lock;
private final Deque free;
private final Deque waiters;
/** Total available memory is the sum of nonPooledAvailableMemory and the number of byte buffers in free * poolableSize. */
private long nonPooledAvailableMemory;
private final Metrics metrics;
private final Time time;
private final Sensor waitTime;
再来看下它的构造器:
/**
* 创建一个缓冲池
*
* @param memory 这个缓冲池最大能调用的内存大小
* @param poolableSize 缓冲池能复用的内存大小
* @param metrics 测量实例
* @param time 时间实例
* @param metricGrpName 测量实例组名
*/
public BufferPool(long memory, int poolableSize, Metrics metrics, Time time, String metricGrpName) {
this.poolableSize = poolableSize;
this.lock = new ReentrantLock();
this.free = new ArrayDeque<>();
this.waiters = new ArrayDeque<>();
this.totalMemory = memory;
this.nonPooledAvailableMemory = memory;
this.metrics = metrics;
this.time = time;
this.waitTime = this.metrics.sensor(WAIT_TIME_SENSOR_NAME);
MetricName rateMetricName = metrics.metricName("bufferpool-wait-ratio",
metricGrpName,
"The fraction of time an appender waits for space allocation.");
MetricName totalMetricName = metrics.metricName("bufferpool-wait-time-total",
metricGrpName,
"The total time an appender waits for space allocation.");
this.waitTime.add(new Meter(TimeUnit.NANOSECONDS, rateMetricName, totalMetricName));
}
接着是本文要讲解的核心方法:
allocate方法的作用:分配指定大小的内存空间给buffer。如果没有足够的内存、缓冲池设置了阻塞模式,这个方法将会被阻塞。
方法的参数:
size :分配的内存大小,maxTimeToBlocksMs:最大阻塞的时长。
public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
// 因为设置了最大可调用的内存,所以说每次分配的内存不能大于它
if (size > this.totalMemory)
throw new IllegalArgumentException("Attempt to allocate " + size
+ " bytes, but there is a hard limit of "
+ this.totalMemory
+ " on memory allocations.");
ByteBuffer buffer = null;
// 考虑到线程安全性问题,用可重入锁加锁
this.lock.lock();
try {
// check if we have a free buffer of the right size pooled
// 如果指定的内存大小等于可重用的内存大小并且双端队列不是空的
// 就取出队列的头部中存储的元素,进行返回。
// 这里的队列充当着缓存的作用
if (size == poolableSize && !this.free.isEmpty())
return this.free.pollFirst();
// now check if the request is immediately satisfiable with the
// memory on hand or if we need to block
// 因为队列里每个元素都存储着ByteBuffer,计算总共可重用的内存大小
// 就要用队列的size乘队列中每个元素的ByteBuffer可重用的内存大小
int freeListSize = freeSize() * this.poolableSize;
// 非可重用的内存+可重用的内存 >= 指定分配的内存
if (this.nonPooledAvailableMemory + freeListSize >= size) {
// we have enough unallocated or pooled memory to immediately
// satisfy the request, but need to allocate the buffer
freeUp(size);
this.nonPooledAvailableMemory -= size;
// 内存不够用,进行阻塞
} else {
// we are out of memory and will have to block
int accumulated = 0;
Condition moreMemory = this.lock.newCondition();
try {
// 剩余的阻塞时长
long remainingTimeToBlockNs = TimeUnit.MILLISECONDS.toNanos(maxTimeToBlockMs);
this.waiters.addLast(moreMemory);
// loop over and over until we have a buffer or have reserved
// enough memory to allocate one
while (accumulated < size) {
long startWaitNs = time.nanoseconds();
long timeNs;
boolean waitingTimeElapsed;
try {
// 阻塞
waitingTimeElapsed = !moreMemory.await(remainingTimeToBlockNs, TimeUnit.NANOSECONDS);
} finally {
long endWaitNs = time.nanoseconds();
// 本次阻塞花费的时长
timeNs = Math.max(0L, endWaitNs - startWaitNs);
this.waitTime.record(timeNs, time.milliseconds());
}
if (waitingTimeElapsed) {
throw new TimeoutException("Failed to allocate memory within the configured max blocking time " + maxTimeToBlockMs + " ms.");
}
// 剩余的阻塞时长
remainingTimeToBlockNs -= timeNs;
// check if we can satisfy this request from the free list,
// otherwise allocate memory
// 非可重用内存不够,再去考虑可重用的内存。如果accumulted等于0
// 并且需要调用的大小size等于可重用内存的大小并且deque不空
if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) {
// just grab a buffer from the free list
// 从双端队列的头部获取ByteBuffer
buffer = this.free.pollFirst();
// 设置收集到的大小为size
accumulated = size;
// 可重用内存也无法满足请求,需要额外分配内存,这些内存不可重用,使用完
// 直接gc
} else {
// we'll need to allocate memory, but we may only get
// part of what we need on this iteration
// 还需要分配size - accumulated大小的内存
// 因为本次操作收集到了accumulated大小的内存
// 也就是为非可重用的内存分配size - accumulated大小的内存
freeUp(size - accumulated);
// 此时的非可重用内存使用的内存大小
// 有可能现在增加了额外的内存之后的非可重用内存还不够用
// 这是就是那句使用部分的内存,分配给非可重用内存一定内存后,即使
// 还不够用,也只使用这部分的内存完成请求
int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
// 现在非可重用的内存剩余内存
this.nonPooledAvailableMemory -= got;
// 收集到的内存
accumulated += got;
}
}
// Don't reclaim memory on throwable since nothing was thrown
// 遇到异常,这里没有抛出,只设置accumulated等于0,回到原点
accumulated = 0;
} finally {
// When this loop was not able to successfully terminate don't loose available memory
// 不释放占用的内存,而是放回到非可重用内存
this.nonPooledAvailableMemory += accumulated;
this.waiters.remove(moreMemory);
}
}
} finally {
// signal any additional waiters if there is more memory left
// over for them
try {
// 还有空闲空间,唤醒下一个线程
if (!(this.nonPooledAvailableMemory == 0 && this.free.isEmpty()) && !this.waiters.isEmpty())
this.waiters.peekFirst().signal();
} finally {
// Another finally... otherwise find bugs complains
lock.unlock();
}
}
if (buffer == null)
// 如果调用内存失败,交给下一个线程执行任务
return safeAllocateByteBuffer(size);
else
return buffer;
}
1.1 freeUp方法:确保有至少指定请求大小的内存,可以通过释放可重用的内存的方式。
private void freeUp(int size) {
while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
this.nonPooledAvailableMemory += this.free.pollLast().capacity();
}
最后这个方法是释放内存:
public void deallocate(ByteBuffer buffer, int size) {
lock.lock();
try {
// 如果使用的完全是可重用的内存,对buffer执行clear操作(nio操作),加入到free list
if (size == this.poolableSize && size == buffer.capacity()) {
buffer.clear();
this.free.add(buffer);
} else {
// 还给非可重用的内存
this.nonPooledAvailableMemory += size;
}
// 唤醒阻塞的线程
Condition moreMem = this.waiters.peekFirst();
if (moreMem != null)
moreMem.signal();
} finally {
lock.unlock();
}
}