树构建算法内存泄漏问题排查与优化求助
问题描述
实现了类操作系统目录结构的树构建算法,核心代码及使用方式如下:
TreeNode 核心实现
public class TreeNode { protected TreeNode(string part) : this(part, -1, null) private TreeNode( string part, int level, TreeNode? parent) { Part = part; Level = level; Parent = parent; Root = parent?.Root ?? this; } private bool _updateEnqueued; public string Part {get;set;} public int Level {get;} public TreeNode? Parent {get;} public TreeNode Root {get;} public bool Add( string[] subTopic, MqttDelivery delivery) => Add(subTopic, Level + 1, delivery); public void ForceResync() => Root.Update(); public void EnqueueUpdate() { if(_updateEnqueued) return; _updateEnqueued = true; Dispatcher.UIThread.Post( Update, DispatcherPriority.Background); } private bool Add(string[] topic, int level, MqttDelivery delivery) { if(level >= topic.Length) { Topic = delivery.Topic; Header = delivery.Header?.ToJsonString(true); Payload = delivery.Payload?.ToJsonString(true); Delivery = delivery; AddToHistory(delivery); return false; } var part = topic[level]; if(!_treeChildren.Value.TryGetValue(part, out var trie)) { TopicsCount++; _treeChildren.Value.Add(part, trie = new TreeNode(part,level,this)); } MessagesCount++; trie.Add(topic, level + 1, delivery); return true; } private void AppendItems() { _updateEnqueued = false; var flatTreeList = new AvaloniaList<TreeNode>(); AppendItems(flatTreeList, this); _visibleChildren = flatTreeList; } private void AppendItems( AvaloniaList<TreeNode> flatTreeList, TreeNode treeNode) { flatTreeList.Add(treeNode); if(!treeNode.IsExpanded) return; foreach(var ch in treeNode._treeChildren.Value) AppendItems(flatTreeList, ch.Value); } public void Update() { AppendItems(); Root.RaisePropertyChanged(nameof(Root.VisibleChildren)); } public void AddToHistory(MqttDelivery mqttMessage) { while(UiHistoryMessages.Count >= 30) UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1); UiHistoryMessages.Insert(0, new ReceivedHistoryMessage{..}); } }
TreeRoot 实现
public class TreeRoot : TreeNode { public TreeRoot() : base("") { var timer = new Timer(1000); timer.Elapsed += OnTimerElapsed; timer.Start(); } private void OnTimerElapsed(object? sender, ElapsedEventArgs e) { Root.EnqueueUpdate(); } }
使用方式
var root = new TreeRoot {IsExpanded = false}; root.ForceResync();
当前问题:持续向树中添加节点后,应用内存从80MB逐步增长至30分钟后的400MB,且无法自动释放。此时节点数约2000个,所有节点的UiHistoryMessages总数约10000条,数据量不大但内存无法回收。
内存泄漏排查与优化方案
1. Timer 未释放导致的内存泄漏
TreeRoot中创建的Timer未正确释放,且Elapsed事件持有对TreeRoot的强引用,导致整个树结构无法被GC回收。
- 优化方案:
让TreeRoot实现IDisposable接口,在Dispose方法中清理Timer:
在应用退出或不再需要树结构时,调用public class TreeRoot : TreeNode, IDisposable { private Timer? _timer; public TreeRoot() : base("") { _timer = new Timer(1000); _timer.Elapsed += OnTimerElapsed; _timer.Start(); } private void OnTimerElapsed(object? sender, ElapsedEventArgs e) { Root.EnqueueUpdate(); } public void Dispose() { if (_timer != null) { _timer.Elapsed -= OnTimerElapsed; _timer.Stop(); _timer.Dispose(); _timer = null; } } }root.Dispose()。
2. 频繁创建AvaloniaList导致的内存堆积
每次Update都会创建新的AvaloniaList<TreeNode>,旧列表若被UI绑定引用未及时释放,会造成内存堆积。
- 优化方案:
复用现有列表,避免频繁创建实例:private void AppendItems() { _updateEnqueued = false; _visibleChildren ??= new AvaloniaList<TreeNode>(); _visibleChildren.Clear(); AppendItems(_visibleChildren, this); }
3. MqttDelivery 实例的不必要持有
Add方法中直接存储完整的MqttDelivery实例,若该对象包含未释放的大资源(如字节数组、流),会持续占用内存。
- 优化方案:
仅存储必要数据,而非整个对象:// 替换原Delivery字段,只保留业务所需信息 public string Topic { get; private set; } public string? HeaderJson { get; private set; } public string? PayloadJson { get; private set; } // 修改Add方法中的赋值逻辑 if(level >= topic.Length) { Topic = delivery.Topic; HeaderJson = delivery.Header?.ToJsonString(true); PayloadJson = delivery.Payload?.ToJsonString(true); // 不再存储Delivery实例 AddToHistory(delivery); return false; }
4. 历史消息的引用优化
ReceivedHistoryMessage若持有对MqttDelivery的引用,即使限制了数量,仍会占用额外内存。
- 优化方案:
历史消息仅存储序列化后的字符串或关键字段:public class ReceivedHistoryMessage { public string Topic { get; set; } public string? HeaderJson { get; set; } public string? PayloadJson { get; set; } public DateTime ReceivedTime { get; set; } // 移除对MqttDelivery的引用 } // 修改AddToHistory方法 public void AddToHistory(MqttDelivery mqttMessage) { while(UiHistoryMessages.Count >= 30) UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1); UiHistoryMessages.Insert(0, new ReceivedHistoryMessage { Topic = mqttMessage.Topic, HeaderJson = mqttMessage.Header?.ToJsonString(true), PayloadJson = mqttMessage.Payload?.ToJsonString(true), ReceivedTime = DateTime.Now }); }
5. Dispatcher.Post 的弱引用优化
EnqueueUpdate中使用Dispatcher.UIThread.Post传递Update方法,若队列中存在未执行的任务,会持有对TreeNode的强引用。
- 优化方案:
使用弱引用包装调用逻辑:public void EnqueueUpdate() { if(_updateEnqueued) return; _updateEnqueued = true; WeakReference<TreeNode> weakRef = new WeakReference<TreeNode>(this); Dispatcher.UIThread.Post(() => { if (weakRef.TryGetTarget(out var node)) { node.Update(); } }, DispatcherPriority.Background); }
6. 工具验证
使用Visual Studio内存诊断工具或dotMemory生成内存快照,确认:
- 占用内存最多的对象类型
- 对象的引用链,定位未被释放的根源
- 是否存在静态字段持有树节点的引用
内容的提问来源于stack exchange,提问作者Anthony
相关产品推荐
相关产品推荐

