分享
HG 堆内内存管理 - 设计与实现 (新)
输入“/”快速插入内容
HG 堆内内存管理
-
设计与实现 (新)
用户8856
用户8856
用户2731
用户2731
用户6445
用户6445
2024年10月30日修改
一、项目需求
1.
Hugegraph 的
读请求
(Gremlin / RESTful API 调用),每次调用都会产生一个 Query 线程。
2.
那么当查询内容数据量过大,就会造成 JVM
Out of Memory
,异常返回。
java.lang.OutOfMemoryError: Java heap space
3.
本需求希望能够对堆内内存的使用情况进行监控和管理,设计一个方案能够简单易用地避免堆内 OOM,保证服务的高可用。
二、整体实现思路
前置的调研/背景和其他方案选择在这就不复述了, 可以参考单独的调研文档, 核心来说我们有两个思路去实现堆内的内存管理:
1.
参考类 Apache IoTDB 的方式, 通过计算每个核心
XXClass
的成员属性大小/估算出预计占据的内存开销, 在所有开销大的地方去进行
预分配检查
(提前限制)
a.
优点:
不使用堆外内存的方法下, 最小代价实现部分
核心模块
内存分配 + 轻管理, 稳定高效/实用
b.
缺点:
代码侵入性高/复用度较低, 所有需要预分配的地方都需要手动 hook /计算, 实现稍复杂
2.
参考 JVM 自己提供的方案, 核心依赖
monitor + interrupt + gc
的三板斧来实现后发限制
a.
优点:
轻量级, 容易理解实现, 侵入性很低, 原本核心代码捕获了 interrupt 异常可直接复用, ROI 高
b.
缺点:
仅能
释放
资源, 不能分配, 标记中断需等待 GC-STW, 监控间隔大可能
中断
不够及时, 太低会影响性能
综上考虑到实际需求/ROI, 以及我们有堆外作为完整/最终方案, 选择轻量级的
JMX
方案
分为两步:Step1:监控堆内内存使用情况;Step2:到达阈值后想办法降低堆内内存使用
1.
实现监控——Java Management Extensions (JMX)
•
ThreadMXBean
ThreadMXBean
接口允许您获取有关 JVM 中运行线程的信息,包括每个线程的堆内内存使用情况。您可以使用
ManagementFactory.getThreadMXBean()
方法获取
ThreadMXBean
实例,然后通过该实例获取每个线程的堆内内存使用情况。
2.
降低内存占用——GC+interrupt
画板
通过 Step1,我们可以检测到堆内内存使用情况的占比,一旦内存占用率超过设定的阈值(如 85%),将采取如下策略试图降低内存占用,以提前预防 oom 的发生:
2.1
GC
由于 gc 的频率问题,在内存占用率到达阈值时,程序并不一定实际使用了 80%的内存,有极大的可能性在已经使用的这部分内存中,有很多对象已经不再被需要,可以被垃圾回收器回收了。因此,当内存占用率达到阈值时,首先使用
System.gc()
建议 jvm 触发 GC。如果有大量的可以被回收的内存,gc 就可以显著降低内存占用,避免 oom。
2.2
中断查询
gc 结束后,我们再次进行二次检查堆内存的使用情况。此时,如果内存占用仍然居高不下,证明程序此时已经没有别的方法腾出内存,迫不得已,只能采取中断查询请求的方式。
•
interrupt(long threadId)
方法
向指定线程发送中断请求
:该方法向具有指定线程 ID 的线程发送一个中断请求。这并不会直接中断线程,而是向线程发送一个中断信号,线程可以选择是否响应中断。
线程如何响应中断
:线程收到中断请求后,可以通过检查
Thread.currentThread().isInterrupted()
或捕获
InterruptedException
异常来响应中断。具体响应方式取决于线程的实现逻辑和设计。(原来的 server 代码中已有埋点,已经存在能够捕获中断并向客户端抛出异常的功能)
三、代码实现
https://github.com/apache/incubator-hugegraph/pull/2650
外部参数:
rest-server.properties
:
代码块
Properties
# 堆内存阈值(1代表禁用此功能)
memory_monitor.threshold=0.85
# 内存监控时间间隔
memory_monitor.period=2000