不会OOM的队列

问题背景

在 Java 多线程编程中,LinkedBlockingQueue 是一个常用的线程安全队列。当没有指定容量时,LinkedBlockingQueue 默认容量为 Integer.MAX_VALUE,这几乎等同于无界队列。在高并发场景下,如果生产者的速度远大于消费者的处理能力,请求将会不断堆积,最终可能导致 JVM 发生 OOM 错误。所以需要在初始化的时候指定一个合理的值,那就比较迷茫了,合理的值的判断标准是什么?\

MemoryLimitedLinkedBlockingQueue

核心思想是通过监控队列中对象的内存占用情况,动态地控制队列的容量,通过限制队列的最大内存占用来防止 OutOfMemory (OOM) 问题

核心组件

  1. MemoryLimiter 类:

    1. maxMemory :队列允许的最大内存占用。

    2. currentMemoryUsage :当前队列的内存占用。

  2. MemoryLimitedLinkedBlockingQueue 类:

    1. 继承自 LinkedBlockingQueue

    2. 重写了 putofferadd 等核心方法,以便在添加元素时检查内存限制。

实现细节

  1. 内存限制的初始化:

    1. MemoryLimitedLinkedBlockingQueue 的构造函数中,初始化 MemoryLimiter 对象,并设置最大内存限制 maxMemory

  2. 内存占用的计算:

    1. 使用 InstrumentationgetObjectSize 方法来计算对象的内存占用。

  3. 核心方法的重写:

    1. put(E e) 方法:先用getObjectSize()计算新对象的内存占用,再用MemoryLimiter.acquireInterruptibly(size) 尝试获取足够的内存

    2. 释放的话就把不用尝试了,直接getObjectSize()计算,然后memoryLimiter.release(size)释放内存

特点:

  1. 优点:防止 OOM:通过限制队列的内存占用,有效防止了 OOM 问题

  2. 缺点:

    1. 用一个sum变量计算空闲内存,每次判断没及时更新最新的内存占用情况

    2. 使用 Instrumentation.getObjectSize() 只能计算对象本身的内存占用,无法考虑对象引用的内存占用,这可能导致实际内存占用高于预期。

    3. 每次添加或移除元素时,都需要计算对象的内存占用,这可能会带来性能开销。

MemorySafeLinkedBlockingQueue

不依赖于复杂的 Instrumentation 技术,而是通过更简单的方式实现内存安全

核心组件

  1. MemoryLimitCalculator 类:

    1. 负责计算和更新当前可用的内存。

    2. 使用ManagementFactoryMemoryMXBean来获取当前 JVM 的可用内存。

    3. 定期刷新可用内存的值,以确保数据的实时性。

  2. MemorySafeLinkedBlockingQueue 类:

    1. 类似MemoryLimitedLinkedBlockingQueue

实现细节

  1. 内存限制的初始化:

    1. MemorySafeLinkedBlockingQueue 的构造函数中,初始化 MemoryLimitCalculator 对象,并设置最大可用内存 maxFreeMemory

  2. 内存占用的计算:

    1. 使用 java.lang.Runtime 类的 freeMemory 方法来获取当前 JVM 的可用内存。

    2. 定期刷新 maxFreeMemory,以确保数据的实时性。

\

相比于MemoryLimitedLinkedBlockingQueue 的优点

  1. MemoryLimitedLBQ 是站在队列的角度限制空间;而MemorySafeLBQ新增了一个成员变量maxFreeMemory用来记录最大的剩余内存,限制的是JVM的剩余空间 ==》规避OOM

  2. 不依赖 Instrumentation,使用的是 ManagementFactory 里面的 MemoryMXBean获取内存的运行状态

  3. 重写了放入元素的 put、offer 方法,并不关注移除元素。 MemoryLimitedLB里面还计算了每个元素的大小,然后搞了一个变量来累加。

==》 优化Map 来做本地缓存,就会放很多元素进去,也会有 OOM 的风险,可以采取这个思路maxFreeMemory 做成动态调整的,模仿线程池参数动态调整

最后更新于