如何无内存泄漏地存储树形节点的历史记录?
内存泄漏问题:树形节点历史记录存储优化
我有一个类树形字典结构,运行时内存从初始100MB快速增长至3GB且无法自动释放。经排查核心问题出在每个节点的历史记录模块——移除if(level >= topic.Length)代码块内的所有逻辑后,内存可正常释放。现咨询如何在无内存泄漏的前提下存储每个树形节点的历史记录,相关代码如下:
树形节点类代码
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; if(parent!=null) MessagesCount = 1; } private int _historyId; public int Level { get; } public TreeNode? Parent { get; } public TreeNode Root { get; } [Reactive] public string Part {get;set;} [Reactive] public string? Header {get;set;} [Reactive] public string? Payload {get;set;} [Reactive] public string? Topic {get;set;} [Reactive] public MqttDelivery? Delivery {get;set;} [Reactive] public int MessagesCount {get;set;} [Reactive] public int TopicsCount {get;set;} public ObservableCollection<ReceivedHistoryMessage> UiHistoryMessages { get; set; } = new(); public readonly Dictionary<string, TreeNode> _treeChildren = new(); public bool Add( string[] subTopic, MqttDelivery delivery) => Add(subTopic, Level + 1, delivery); 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.TryGetValue(part, out var trie)) { TopicCount++; _treeChildren.Add(part, trie = new TreeNode(part, level, this)); } MessagesCount++; trie.Add(topic, level+1, delivery); return true; } private void AddToHistory(MqttDelivery mqttMessage) { while(UiHistoryMessages.Count >= 30) UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1); UiHistoryMessages.Insert(0, new ReceivedHistoryMessage() { Id = _historyId++, Time = DateTime.Now, Message = mqttMessage, UIFormattedMessage = $"{DateTime.Now:mm:ss}, {mqttMessage.Header.ToJsonString()}" }); } }
历史消息及MQTT对象定义
public class ReceivedHistoryMessage { public int Id { get; set; } public string? UIFormattedMessage { get; set; } public DateTime Time { get; set; } public MqttMessage? Message { get; set; } } [Name( "message" )] public partial class MqttMessage { public MqttMessageHeader Header { get; set; } = new(); public JObject Payload { get; set; } }
核心问题分析
当前内存泄漏的根源:
ObservableCollection<ReceivedHistoryMessage>对MqttDelivery/MqttMessage持有强引用,而树形节点本身长期存活,导致历史消息无法被GC回收- 即使移除旧历史项,若UI绑定未正确解除,
ObservableCollection的项仍可能被UI上下文持有,无法释放 - 树形节点自身已存储
Delivery、Header等数据,历史记录重复存储完整大对象,加剧内存占用
无泄漏优化方案
1. 弱引用存储历史消息
将历史消息中的原始MQTT对象改为弱引用,避免强引用阻碍GC回收:
public class ReceivedHistoryMessage { public int Id { get; set; } public string? UIFormattedMessage { get; set; } public DateTime Time { get; set; } // 用弱引用存储原始消息,不强制持有对象 public WeakReference<MqttMessage>? MessageRef { get; set; } // 可选:提供安全获取消息的方法 public MqttMessage? GetMessage() { MessageRef?.TryGetTarget(out var msg); return msg; } } // 修改AddToHistory方法 private void AddToHistory(MqttDelivery mqttMessage) { while(UiHistoryMessages.Count >= 30) UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1); UiHistoryMessages.Insert(0, new ReceivedHistoryMessage() { Id = _historyId++, Time = DateTime.Now, MessageRef = new WeakReference<MqttMessage>(mqttMessage.Message), UIFormattedMessage = $"{DateTime.Now:mm:ss}, {mqttMessage.Header.ToJsonString()}" }); }
2. 优化集合类型或手动清理
如果UI不需要实时响应集合变化,用List<ReceivedHistoryMessage>替代ObservableCollection(后者会维护额外事件订阅,易引发泄漏):
// 替换原ObservableCollection public List<ReceivedHistoryMessage> UiHistoryMessages { get; set; } = new();
若必须保留ObservableCollection,需给TreeNode添加清理方法,在节点废弃时主动释放资源:
public void Cleanup() { UiHistoryMessages.Clear(); // 递归清理子节点 foreach(var child in _treeChildren.Values) { child.Cleanup(); } _treeChildren.Clear(); }
3. 避免重复存储大对象
利用树形节点已有的Header、Topic等数据,历史记录只存储UI展示必要的信息,不持有完整MQTT对象:
public class ReceivedHistoryMessage { public int Id { get; set; } public string? UIFormattedMessage { get; set; } public DateTime Time { get; set; } public string? Topic { get; set; } public string? HeaderJson { get; set; } } // 修改AddToHistory方法 private void AddToHistory(MqttDelivery mqttMessage) { while(UiHistoryMessages.Count >= 30) UiHistoryMessages.RemoveAt(UiHistoryMessages.Count - 1); var headerJson = mqttMessage.Header?.ToJsonString(); UiHistoryMessages.Insert(0, new ReceivedHistoryMessage() { Id = _historyId++, Time = DateTime.Now, Topic = mqttMessage.Topic, HeaderJson = headerJson, UIFormattedMessage = $"{DateTime.Now:mm:ss}, {headerJson}" }); }
4. 检查Reactive属性订阅
代码中使用的[Reactive]属性会生成订阅者列表,需确保所有订阅节点属性变化的代码,在不需要时调用Dispose取消订阅,避免残留引用导致泄漏。
内容的提问来源于stack exchange,提问作者Anthony
相关产品推荐
相关产品推荐

