不会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 做成动态调整的,模仿线程池参数动态调整
最后更新于