我一直在撞墙,试图弄清楚我在垃圾收集的Cocoa应用程序中是如何发生内存泄漏的。 (活动监视器中的内存使用量会增长和增长,使用GC监视器工具运行应用程序也会显示不断增长的图形。)
我最终将其缩小为我的代码中的单个模式。数据被加载到NSData中,然后由C库解析(数据的字节和长度被传递到它中)。 C库具有回调函数,它将触发并返回子字符串的起始指针和长度(以避免内部复制)。但是,出于我的目的,我需要将它们转换为NSStrings并保持一段时间。我是通过使用NSString的initWithBytes:length:encoding:方法完成的。我假设会复制字节,而NSString会适当地管理它,但是出了点问题,因为这会像疯了一样泄漏。
此代码将“泄漏”或以某种方式欺骗垃圾收集器:
- (void)meh
{
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]];
const int substrLength = 80;
for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
[cocoaString length];
}
}
我可以将它放在计时器中,只需观察活动监视器以及GC监视器仪器的内存使用情况。 (holmes.txt是594KB)
这不是世界上最好的代码,但它显示了问题。 (我正在运行10.6,该项目的目标是10.5 - 如果这很重要)。我阅读了垃圾收集文档并注意到了一些可能的陷阱,但我认为我没有做任何明显违反规则的事情。不过要问,不要伤心。谢谢!
这是对象图的增长和增长的图片:
答案 0 :(得分:13)
这是一个不幸的边缘案例。请提交一个错误(http://bugreport.apple.com/)并附上您出色的最佳示例。
问题有两个;
主事件循环未运行,因此,不通过MEL活动触发收集器。这使收集器执行其正常的仅基于阈值的集合。
数据将从文件读取的数据存储到从malloc区域分配的malloc缓冲区中。因此,GC会计分配 - NSData对象本身 - 非常小,但指向非常大的东西(malloc分配)。最终结果是收集器的阈值没有被击中而且它没有被收集。显然,希望改善这种行为,但这是一个难题。
这是一个非常容易在微基准或孤立中重现的错误。在实践中,通常会有足够的事情发生,这个问题不会发生。但是,在某些情况下可能会出现问题。
将您的代码更改为此,收集器将收集数据对象。请注意,您不应经常使用collectExhaustively
- 它会占用CPU。
- (void)meh
{
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"holmes" ofType:@"txt"]];
const int substrLength = 80;
for (const char *substr = [data bytes]; substr-(const char *)[data bytes] < [data length]; substr += substrLength) {
NSString *cocoaString = [[NSString alloc] initWithBytes:substr length:substrLength encoding:NSUTF8StringEncoding];
[cocoaString length];
}
[data self];
[[NSGarbageCollector defaultCollector] collectExhaustively];
}
[data self]
使数据对象在最后一次引用后保持活动状态。