MemoryLimitedLinkedBlockingQueue🚩
这是dubbo源码用到的阻塞队列。继承自 LinkedBlockingQueue。
重写了LinkedBlockingQueue的核心方法,但都是通过MemoryLimiter去做的。
它的目的就是限制队列的内存上限。
// 所有字段如下:
public class MemoryLimiter {
// 可以粗略的获取要放入阻塞队列的对象的内存大小
private final Instrumentation inst;
// 该阻塞队列的总内存限制大小
private long memoryLimit;
// 当前使用的内存大小
private final LongAdder memory;
// 常规的锁相关,Condition
private final ReentrantLock acquireLock;
private final Condition notLimited;
private final ReentrantLock releaseLock;
private final Condition notEmpty;
}
核心方法(只放出核心部分):
public void acquireInterruptibly(Object e) throws InterruptedException {
// 加锁
this.acquireLock.lockInterruptibly();
try {
// 利用Instrumentation估算对象内存大小
long objectSize = this.inst.getObjectSize(e);
// 判断内存超限
while(this.memory.sum() + objectSize >= this.memoryLimit) {
this.notLimited.await();
}
this.memory.add(objectSize);
if (this.memory.sum() < this.memoryLimit) {
this.notLimited.signal();
}
} finally {
// 解锁
this.acquireLock.unlock();
}
if (this.memory.sum() > 0L) this.signalNotEmpty();
}
}
// 1.获取锁
// 2.如果放入该对象到队列会超出内存限制,阻塞
// 3.内存大小小于内存限制,会尝试唤醒
小结
其实就是保证了队列的总占用内存大小不超过限制。
MemorySafeLinkedBlockingQueue
为了替代 MemoryLimitedLinkedBlockingQueue而生。
它不依赖于Instrumentation获取内存大小。
相反,它从JVM的角度考虑,maxFreeMemory变量代表最大的剩余内存
public class MemorySafeLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
private static final long serialVersionUID = 8032578371739960142L;
public static int THE_256_MB = 268435456;
private int maxFreeMemory;
private Rejector<E> rejector;
那么怎么获取内存信息呢
利用MemoryLimitCalculator类。
public class MemoryLimitCalculator {
private static volatile long maxAvailable;
private static final AtomicBoolean refreshStarted = new AtomicBoolean(false);
public MemoryLimitCalculator() {
}
}
MemorySafe实现原理
在执行offer/put方法前,都会执行hasRemainedMemory()方法。
而put方法在hasRemainedMemory()return true之后直接调用了父类的方法。
public boolean hasRemainedMemory() {
return MemoryLimitCalculator.maxAvailable() > (long)this.maxFreeMemory;
}
public void put(final E e) throws InterruptedException {
if (this.hasRemainedMemory()) {
// 是直接调用的父类方法,而不像MemoryLimited直接完全重写了
// 因为它的设计理念是只关心添加元素时候的剩余空间大小,它甚至都不会去关注当前这个元素的大小。
super.put(e);
} else {
this.rejector.reject(e, this);
}
}
那么核心问题就在于MemoryLimitCalculator.maxAvailable()是怎么实现的
private static void refresh() {
maxAvailable = Runtime.getRuntime().freeMemory();
}
private static void checkAndScheduleRefresh() {
if (!refreshStarted.get()) {
refresh();
if (refreshStarted.compareAndSet(false, true)) {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Dubbo-Memory-Calculator"));
scheduledExecutorService.scheduleWithFixedDelay(MemoryLimitCalculator::refresh, 50L, 50L, TimeUnit.MILLISECONDS);
GlobalResourcesRepository.registerGlobalDisposable(() -> {
refreshStarted.set(false);
scheduledExecutorService.shutdown();
});
}
}
}
// MemoryLimitCalculator.maxAvailable()方法
public static long maxAvailable() {
checkAndScheduleRefresh();
return maxAvailable;
}
// private static final AtomicBoolean refreshStarted = new AtomicBoolean(false);
// 这个变量用来表示MemoryLimitCalculator是否开始工作
// 如果为false,会先refresh,然后开启线程池每 50ms 运行一次的定时任务
// 定时任务的内容是 触发一下 refresh 方法 保证 maxAvilable 参数的实时性
// 因此对于maxAvailable()方法,如果refreshStarted 为 true,直接返回字段maxAvailable的值
// 否则会先refresh然后定时更新,再返回maxAvailable的值
至此MemoryLimitCalculator源码分析完毕
小结
MemoryLimitCalculator通过线程池定时任务保证我们能够获得JVM剩余空闲内存的大小。
MemorySafeLinkedBlockingQueue只是在真正调用LinkedBlockingQueue的put方法前,判断JVM剩余内存是否足够。判断利用了MemoryLimitCalculator而非Instrumentation。
MemorySafeLinkedBlockingQueue只关心剩余内存够用吗,从全局角度考虑
MemoryLimitedLinkedBlockingQueue只关心队列的内存是否超出限制,专注于队列本身