Chronicle Queue分钟滚动删除文件后仍在lsof列表未释放内存问题
问题根因
Chronicle Queue 5.x版本的onReleased回调仅代表队列内核逻辑不再主动持有该周期文件的引用,并不代表所有进程侧的内存映射句柄已经释放:
- Linux系统下内存映射类型的文件,只要还有任意进程持有映射引用,执行
file.delete()仅会将文件标记为待删除,不会真正释放磁盘空间和文件句柄,lsof中会显示为(deleted)状态 - 手动调用
storeForCycle获取旧周期存储实例的操作,反而会主动创建对旧周期文件的新引用,导致资源无法释放 - OpenJDK 11的MappedByteBuffer回收依赖GC触发,若JVM缓存了旧周期的映射 buffer,也会导致文件句柄迟迟不释放
修复方案
- 第一,删除手动调用
storeForCycle关闭旧存储的逻辑,改为通过tailer消费进度自动触发资源释放 - 第二,调整初始化配置,增加空闲存储自动释放逻辑,示例代码如下:
eventStore = SingleChronicleQueueBuilder.binary(GlobalConstants.CURRENT_DIR + GlobalConstants.PATH_SEPARATOR + EventBusConstants.EVENT_DIR + GlobalConstants.PATH_SEPARATOR + eventType) .rollCycle(RollCycles.MINUTELY) .storeFileListener(storeFileListener) // 新增:配置周期文件空闲1分钟后自动释放所有映射引用 .storeFileIdleTimeout(Duration.ofMinutes(1)) .build();
- 第三,调整周期资源回收逻辑,每次确认tailer滚动到新周期后,主动释放所有旧周期资源:
// 每次消费完消息后执行 int currentCycle = tailer.cycle(); if (currentCycle > previousCycle) { // 释放所有早于当前周期的存储资源、内存映射 ((SingleChronicleQueue) eventStore).releaseResourcesForCyclesLessThan(currentCycle); previousCycle = currentCycle; }
- 第四,优化
StoreFileListener删除逻辑,确认没有活跃tailer引用旧周期后再删除文件:
storeFileListener = new StoreFileListener() { @Override public void onReleased(int cycle, File file) { // 确保当前tailer已经消费到更新的周期,没有引用待删除文件 if (tailer.cycle() > cycle) { file.delete(); } } };
- 第五,调整JVM启动参数,加速内存映射回收:
新增以下参数,避免JDK缓存大量映射buffer导致资源不释放:-Djdk.nio.maxCachedBufferSize=262144 -XX:MaxDirectMemorySize=4G # 根据实际内存配置,建议不小于物理内存的1/4 # 不要添加-XX:+DisableExplicitGC参数,Chronicle Queue依赖显式GC触发旧映射回收
验证方式
修改后运行服务,每隔5分钟执行lsof -p <进程ID> | grep deleted查看待删除文件列表,若旧周期的队列文件在被标记删除后1~2分钟内从列表中消失,即修复成功。
内容的提问来源于stack exchange,提问作者chaitas shah
相关产品推荐
相关产品推荐

