流式回收:释放越积极不等于内存越稳定
自动关联目录:流式回收:释放越积极不等于内存越稳定
核心判断:流出策略不能只看“当前离玩家多远”。过度释放会把内存问题转换成重复下载、解码、UObject 创建和渲染资源重建;真正稳定的策略必须同时观察内存压力与重载成本。
一、业务冲突:保留会涨内存,释放会抖帧
小场景或短会话中,离开半径后保留已驻留对象可以避免折返重载;大场景、低内存设备或长会话中,不主动释放又会使工作集只增不减。统一策略无法同时适应地图密度、资产体量、移动速度和设备内存。
二、实际实现:双水位、趋势与重复加载已经进入同一状态机
实践中的流出策略不是单一距离阈值。它根据场景对象量、空间跨度、资产估算和移动端标志选择默认强度;按设备物理内存推导高低水位;内存接近高水位且持续增长时进入压力态,只有连续低于低水位一段时间才退出。距离判断还带空间迟滞。
策略同时记录对象流出时间,在窗口内统计重复流入比例。重复加载过高时转向保留,避免边界往返不断支付加载成本;内存压力优先级更高,可以收缩到更小保护半径。性能快照还分开记录进程内存、已知资源估算、流入/流出速率和内存恢复延迟。
三、值得保留:这是控制系统,不是一条 if
- 高低水位避免压力状态在单一阈值附近抖动。
- 内存趋势要求连续样本,不会因一次采样毛刺立即降级。
- 重复加载保护承认“释放”本身有成本。
- 空间迟滞、时间冷却和策略强度分别控制不同振荡源。
- 已知 Mesh/Material/Texture 按唯一资源计费,避免共享资产重复估算。
- 进程内存与资源估算并存,没有把估算值伪装成平台真实占用。
四、结构性缺陷:进程内存只能报警,不能归因
UsedPhysical 包含脚本堆、UObject、物理、音频、网络缓存和引擎分配器滞留。它上涨不证明流式资产是原因;Actor 销毁后,资源也可能等待 GC 和渲染线程释放。因此从“流出对象数”不能直接推导“本帧释放字节数”。
当前重复加载率按对象次数统计,也会把便宜小物件和昂贵大资产等价计数。若许多对象共享同一 Mesh,频繁对象进出未必产生资源重载;反之,一个大纹理反复重建可能很昂贵但次数很少。
五、目标设计:以 Residency Ticket 和重建成本做决策
对象流出先撤销 Actor/可见表示,再释放它持有的资源票据;只有最后一个票据释放,资源才进入可回收状态。策略按唯一资源统计 ReloadCost 和 ReclaimableBytes,而不是只按对象数量。
for (Resource r : evictionCandidates) {
r.score = EvictionScore(r.bytes, r.decodeUs, r.commitUs,
r.lastUse, r.predictedReuse, memoryPressure);
}
EvictWithinBudget(SortDescending(evictionCandidates));发生内存压力时先淘汰“可回收大、重建便宜、近期复用概率低”的资源;对角色、交互对象和正在使用的代理保留 Pin。GC 只在批次边界或真正需要时触发,不能每流出一个对象就强制回收。
六、验证:释放必须带来可测的净收益
指标应包含 ReclaimEligibleBytes、GC 后实际内存下降、资源级重复加载字节、解码与主线程重建时间、边界往返次数、恢复延迟和因回收造成的可见空洞。合成示例:释放 100 MiB 后短时间又重载 80 MiB,若重建还造成帧尖峰,则净收益远小于对象流出日志显示的数量;示例不代表历史数据。
最终决策规则:流出不是成功,只有在可接受重建成本下形成可观测的内存回收,才是有效淘汰。