Cocoa应用产生大量inactive内存问题的排查与解决咨询
问题
我用Objective-C开发分块读取处理文件的软件,处理大文件时速度暴跌。通过Activity Monitor观察,系统内存的蓝色区域(inactive内存)会被完全占满,直到绿色空闲内存耗尽,随后开始大量换页,磁盘使用率拉满。
我知道inactive内存是应用用过、关闭后仍保留的内存,再次启动时会归还给应用。我用了ARC,所有malloc/calloc都手动free确保内存尽快释放,Instruments检测也显示无内存泄漏。推测macOS把我释放的内存标记为inactive,觉得应用还会用到。循环里已经加了自动释放池,理论上应该能正确释放内存。
复现代码如下:
// // main.m // memory issue // // Created by Lasse Lauwerys on 21/04/24. // Copyright (c) 2024 Lasse Lauwerys. All rights reserved. // #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Reading file..."); UInt32 bufferSize = 200000000; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:@"/path/to/large/file"]; UInt64 bytesToProcess; NSData *data; do @autoreleasepool { // This loop reads the file into ram in chunks of 200MB and frees it after each iteration. bytesToProcess = [data = [fileHandle readDataOfLength:bufferSize] length]; } while (bytesToProcess); NSLog(@"Finsihed!"); } return 0; }
哪怕只处理2GB文件,inactive内存都会被占满100%。谷歌搜不到有效解决办法,我觉得这不是内存泄漏,因为inactive内存本应是备用空闲内存。用purge命令能释放内存、停止换页,速度会恢复,但没多久蓝色区域又会被占满。
想问:这是malloc还是Objective-C类实例创建导致的?怎么阻止系统把已释放内存标记为inactive?
解答
问题根源
既不是malloc也不是Objective-C实例创建的问题,这是macOS内存管理机制的正常行为。当你释放内存后,系统不会立刻把它归为“空闲”,而是标记为inactive——这是系统的缓存策略,目的是如果后续你再需要相同内存块时,能更快分配,避免重复初始化的开销。
解决办法
改用底层内存分配与读取方式
放弃NSData,直接用malloc分配缓冲区,配合底层read函数读取文件。这类内存释放后会直接归还给系统,不会被标记为inactive。修改后的代码示例:int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Reading file..."); UInt32 bufferSize = 200000000; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:@"/path/to/large/file"]; ssize_t bytesRead; void *buffer = malloc(bufferSize); do { bytesRead = read([fileHandle fileDescriptor], buffer, bufferSize); } while (bytesRead > 0); free(buffer); NSLog(@"Finished!"); } return 0; }强制让系统回收NSData占用的内存
如果你坚持用NSData,可以在处理完每块数据后,调用msync函数告知系统不需要缓存这块内存:do @autoreleasepool { data = [fileHandle readDataOfLength:bufferSize]; bytesToProcess = data.length; if (bytesToProcess > 0) { msync((void*)data.bytes, bytesToProcess, MS_INVALIDATE); } } while (bytesToProcess);这个操作会让系统把NSData占用的内存从inactive转为空闲,注意要在data对象释放前调用。
缩小分块大小
把200MB的分块调整为更小的尺寸(比如32MB或64MB),每次占用的内存更少,inactive内存不会快速累积到触发换页的程度,系统也更容易在需要时回收这些小块内存。
关键说明
macOS的inactive内存设计初衷是提升性能,只有当系统真正需要内存时,才会自动回收inactive内存。但大文件分块读取场景下,系统的缓存判断逻辑会误判,导致内存被持续占用。上述方法都是绕过或调整系统的缓存策略,让释放的内存能快速回到空闲池。
内容的提问来源于stack exchange,提问作者Foxyz

