不会OOM的队列
问题背景
在 Java 多线程编程中,LinkedBlockingQueue
是一个常用的线程安全队列。当没有指定容量时,LinkedBlockingQueue
默认容量为 Integer.MAX_VALUE
,这几乎等同于无界队列。在高并发场景下,如果生产者的速度远大于消费者的处理能力,请求将会不断堆积,最终可能导致 JVM 发生 OOM 错误。所以需要在初始化的时候指定一个合理的值,那就比较迷茫了,合理的值的判断标准是什么?\
MemoryLimitedLinkedBlockingQueue
MemoryLimitedLinkedBlockingQueue
核心思想是通过监控队列中对象的内存占用情况,动态地控制队列的容量,通过限制队列的最大内存占用来防止 OutOfMemory (OOM) 问题
核心组件
MemoryLimiter
类:maxMemory
:队列允许的最大内存占用。currentMemoryUsage
:当前队列的内存占用。
MemoryLimitedLinkedBlockingQueue
类:继承自
LinkedBlockingQueue
。重写了
put
、offer
、add
等核心方法,以便在添加元素时检查内存限制。
实现细节
内存限制的初始化:
在
MemoryLimitedLinkedBlockingQueue
的构造函数中,初始化MemoryLimiter
对象,并设置最大内存限制maxMemory
。
内存占用的计算:
使用
Instrumentation
的getObjectSize
方法来计算对象的内存占用。
核心方法的重写:
put(E e)
方法:先用getObjectSize()
计算新对象的内存占用,再用MemoryLimiter.acquireInterruptibly(size)
尝试获取足够的内存释放的话就把不用尝试了,直接
getObjectSize()
计算,然后memoryLimiter.release(size)
释放内存
特点:
优点:防止 OOM:通过限制队列的内存占用,有效防止了 OOM 问题
缺点:
用一个sum变量计算空闲内存,每次判断没及时更新最新的内存占用情况
使用
Instrumentation.getObjectSize()
只能计算对象本身的内存占用,无法考虑对象引用的内存占用,这可能导致实际内存占用高于预期。每次添加或移除元素时,都需要计算对象的内存占用,这可能会带来性能开销。
MemorySafeLinkedBlockingQueue
不依赖于复杂的 Instrumentation
技术,而是通过更简单的方式实现内存安全
核心组件
MemoryLimitCalculator
类:负责计算和更新当前可用的内存。
使用
ManagementFactory
的MemoryMXBean
来获取当前 JVM 的可用内存。定期刷新可用内存的值,以确保数据的实时性。
MemorySafeLinkedBlockingQueue
类:类似
MemoryLimitedLinkedBlockingQueue
类
实现细节
内存限制的初始化:
在
MemorySafeLinkedBlockingQueue
的构造函数中,初始化MemoryLimitCalculator
对象,并设置最大可用内存maxFreeMemory
。
内存占用的计算:
使用
java.lang.Runtime
类的freeMemory
方法来获取当前 JVM 的可用内存。定期刷新
maxFreeMemory
,以确保数据的实时性。
\
相比于MemoryLimitedLinkedBlockingQueue
的优点
MemoryLimitedLBQ 是站在队列的角度限制空间;而MemorySafeLBQ新增了一个成员变量
maxFreeMemory
用来记录最大的剩余内存,限制的是JVM的剩余空间 ==》规避OOM不依赖 Instrumentation,使用的是 ManagementFactory 里面的 MemoryMXBean获取内存的运行状态
重写了放入元素的 put、offer 方法,并不关注移除元素。 MemoryLimitedLB里面还计算了每个元素的大小,然后搞了一个变量来累加。
==》 优化Map 来做本地缓存,就会放很多元素进去,也会有 OOM 的风险,可以采取这个思路maxFreeMemory 做成动态调整的,模仿线程池参数动态调整
最后更新于