macOS应用程序中奇怪的内存泄漏问题求助
超大型XML解析的内存泄漏问题排查与修复
问题背景
- 处理1.7TB超大型XML文件,原生
XMLParser解析至5700万行时卡住,内存占用稳定在7-7.2GB(M1芯片,8GB物理内存) - 自行实现流解析后,处理3-4GB内容时内存飙升至12-13GB,最终突破20GB被迫终止
核心内存问题分析
- 日志数组无限累积:
logs作为@State数组持续存储所有日志条目,无任何清理逻辑,随着处理量增加,内存会持续上涨 - 字符串创建错误:使用
String(bytesNoCopy:)时,错误传入缓冲区总长度l(128MB)而非实际读取的numberOfBytesRead,导致字符串包含大量无效数据,额外占用内存 - 临时对象未及时回收:并发队列循环中未手动管理自动释放池,拆分字符串生成的大量临时对象无法及时回收,堆积在内存中
- Unsafe指针未释放:手动分配的
UnsafeMutablePointer<UInt8>未调用deallocate(),造成永久内存泄漏 - 队列自动释放策略不合理:队列
autoreleaseFrequency设为.inherit,无法及时清理任务产生的临时对象
修复方案
1. 限制内存日志数量
避免将所有日志存入内存,只保留最近N条或改用文件日志:
// 替换原logs.append逻辑 DispatchQueue.main.async { self.logs.append("[\(count)] String buffer count: \(str.count), Lines count: \(lines.count)") if self.logs.count > 100 { // 仅保留最新100条日志 self.logs.removeFirst() } }
2. 修正字符串创建逻辑
使用实际读取的字节数创建字符串,避免无效内存占用:
str = String(bytesNoCopy: buf, length: numberOfBytesRead, encoding: .utf8, freeWhenDone: false) ?? ""
3. 添加自动释放池管理临时对象
在循环内部加入autoreleasepool,确保每次循环的临时对象及时回收:
while inputStream.hasBytesAvailable { autoreleasepool { // 原循环内的所有处理逻辑 } }
4. 释放Unsafe指针
通过defer确保手动分配的缓冲区最终被释放:
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: l) defer { buf.deallocate() } // 函数结束时自动释放
5. 调整队列自动释放频率
将队列的自动释放策略改为.workItem,让每个任务的临时对象及时清理:
let queue = DispatchQueue(label: "Parser", qos: .userInteractive, attributes: .concurrent, autoreleaseFrequency: .workItem)
修复后核心代码片段
func parseData(from inputStream: InputStream) { queue.async { var count = 0 let l = 128*1024*1024 let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: l) defer { buf.deallocate() } var lastLine = "" var str = "" var lines: [String] = [] inputStream.open() defer { inputStream.close() } while inputStream.hasBytesAvailable { autoreleasepool { let numberOfBytesRead = inputStream.read(buf, maxLength: l) guard numberOfBytesRead >= 0 else { if let error = inputStream.streamError { print(error) } return } str = String(bytesNoCopy: buf, length: numberOfBytesRead, encoding: .utf8, freeWhenDone: false) ?? "" lines = (lastLine + str).components(separatedBy: "\n").filter{$0.count > 3} lastLine = lines.removeLast() DispatchQueue.main.async { self.logs.append("[\(count)] String buffer count: \(str.count), Lines count: \(lines.count)") if self.logs.count > 100 { self.logs.removeFirst() } } while !lines.isEmpty { var line = lines.removeFirst() while line.first ?? "@" == " " { line.removeFirst() } if line.first ?? "@" == "<" { line.removeFirst() } if line.last ?? "@" == ">" { line.removeLast() } var name = "" while line.first ?? "@" != " " && !line.isEmpty { name.append(line.removeFirst())} if line.first ?? "@" == " " { line.removeFirst() } let array = line.components(separatedBy: "\" ") if array.count == 1 && name.first ?? "@" == "/" { name.removeFirst() self.didEndElement(name) } else { var attributes: [String: String] = [:] for item in array { var dic = item.components(separatedBy: "=\"") if dic.count == 2 { if dic[1].last ?? "@" == "/" { dic[1].removeLast() } if dic[1].last ?? "@" == "\"" { dic[1].removeLast() } attributes[dic[0]] = dic[1] } } self.didStartElement(name, attributes: attributes) if line.last ?? "@" == "/" { self.didEndElement(name) } } } count += 1 } } } }
内容的提问来源于stack exchange,提问作者diclonius9
相关产品推荐
相关产品推荐

