核心数据导入-内存不释放

13

关于Core Data和内存无法释放的问题。我的问题是在从返回JSON的WebService中导入数据时进行同步处理。我将要导入的数据加载到内存中,循环遍历并创建NSManagedObjects。导入的数据需要创建与其他对象关联的对象,在总共大约11,000个对象中,但为了隔离问题,我现在只创建第一层和第二层的项目,而不考虑关系,这些是9043个对象。

我开始检查使用的内存量,因为应用程序在进程结束时崩溃(具有完整数据集)。第一个内存检查是在将json加载到内存后,因此测量真正只考虑了对象的创建和插入到Core Data中。我用于检查使用的内存是这段代码(来源

  -(void) get_free_memory {
struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size); if( kerr == KERN_SUCCESS ) { NSLog(@"Memory in use (in bytes): %f",(float)(info.resident_size/1024.0)/1024.0 ); } else { NSLog(@"Error with task_info(): %s", mach_error_string(kerr)); } }

我的设置:

  • 1 持久存储协调者
  • 1 主要托管对象上下文(MMC)(使用NSMainQueueConcurrencyType仅用于在应用程序中读取数据)
  • 1 后台托管对象上下文(BMC)(NSPrivateQueueConcurrencyType,undoManager设置为nil,用于导入数据)

BMC独立于MMC,因此BMC不是MMC的子上下文。它们也不共享任何父上下文。我不需要BMC通知MMC进行更改。因此,BMC只需要创建/更新/删除数据。

平台:

  • iPad 2和3
  • iOS,我已将部署目标设置为5.1和6.1。没有区别
  • XCode 4.6.2
  • ARC

问题: 导入数据时,使用的内存不停地增加,并且iOS似乎无法释放内存,即使在处理结束后。如果数据样本增加,这将导致内存警告并在关闭应用程序后。

研究:

  1. Apple文档

    这是一个很好的回顾,在将数据导入到Core Data时需要考虑的要点。(来源:Stackoverflow)

    进行的测试和内存释放分析。他似乎和我有同样的问题,并向苹果发送了错误报告,但尚未收到回复。(来源:Source)

    导入并显示大型数据集(来源:Source)

    指出了导入大量数据的最佳方法。虽然他提到:

    "我可以在稳定的3MB内存中导入数百万条记录,而无需调用-reset."

    这让我认为这可能是可能的?(来源:Source)

    测试:

    数据样本:创建总共9043个对象。

    • 关闭关系的创建,因为文档中说它们是“昂贵”的
    • 没有进行任何获取操作

    代码:

    
    - (void)processItems {
        [self.context performBlock:^{
            for (int i=0; i < [self.downloadedRecords count];) {
                @autoreleasepool
                {
                    [self get_free_memory]; // 显示当前内存使用情况
                    for (NSUInteger j = 0; j < batchSize && i < [self.downloadedRecords count]; j++, i++)
                    {
                        NSDictionary *record = [self.downloadedRecords objectAtIndex:i];
    
                        Item *item=[self createItem];
                        objectsCount++;
    
                        // 从record中填充项对象的数据,不创建关系
                        [self updateItem:item WithRecord:record];
    
                        // 创建子项,从record中填充数据,并关闭关系创建
                        [self processSubitemsWithItem:item AndRecord:record]; 
                    }
                    // 在释放自动释放池之前执行上下文保存,如研究5)所述
                    [self.context save:nil];
    
                    // 将所有已创建的项反归为虚址状态
                    for (NSManagedObject *object in [self.context registeredObjects]) {
                        [self.context refreshObject:object mergeChanges:NO];
                    }
                    // 重置上下文,以二次执行前面的操作
                    [self.context reset];
                }
            }
        }];
        [self check_memory];// 通过多次调用 [self get_free_memory] 方法查看同步后内存情况
    }

    测量:

    同步前,内存从16.97 MB到30 MB之间波动,同步后下降至28 MB。每5秒重复调用get_memory函数可将内存保持在28 MB。

    其他尝试但没有成功的测试:

    • 按照研究2)中的指示重新创建持久化存储区域无效
    • 尝试让线程等待一段时间以查看内存是否恢复,例如第4个例子
    • 在整个过程结束后将上下文设置为nil
    • 在任何时候都不保存上下文完成整个过程(因此失去信息)。实际上,这产生了保持少量内存的结果,将其保持在20 MB左右。但它仍然不会减少...我需要存储的信息 :)

    也许我错过了什么,但我真的测试了很多次,按照指南进行操作后,我希望再次看到内存减少。我已经运行了分配工具来检查堆增长,这也似乎没问题。也没有内存泄漏。

    我正在思考要测试/调整的想法...如果有人能帮我提出其他测试的想法,或者指出我做错了什么,我将非常感激。或者它就像这样,应该这样工作...我怀疑...

    谢谢任何帮助。

    编辑

    我使用工具来使用Activity Monitor模板分析内存使用情况,并且在“实际内存使用情况”中显示的结果与使用get_free_memory 打印到控制台中的结果相同,但内存似乎仍然没有被释放。


最好使用Instruments分析内存使用情况,而不是使用该函数。除了在不同时间显示内存使用情况外,它还将把内存分配与特定代码行相关联。 - Tom Harrington
此外,看起来内部循环的每次迭代都将处理相同的记录,因为在该循环期间i不会改变。 - Tom Harrington
嗨Tom,感谢您的回复。我已经使用Allocations模板和Leak模板对应用程序进行了堆增长分析,一切看起来都很好...您建议使用Memory monitor吗?我对该分析工具并不是很熟悉。实际上,在第二个循环中,i每次递增1(您需要滚动查看,代码无法完全适应框架)。这样做,我能够在@autoreleasepool块结束之前保存批处理,这是我在Research 5)中读到的建议。 - Tamara Bernad
2个回答

10

嗯,这很尴尬... 在方案中启用了“僵尸”对象,在参数中它们被关闭,但在诊断中,“启用僵尸对象”被选中...

关闭它可以保持内存稳定。

感谢那些阅读问题并尝试解决它的人!


有些尴尬...但是你救了我免去了另外几个小时的挣扎...我完全忘记了我已经启用了它们,一直在疯狂地试图弄清楚为什么我的应用程序会占用内存! - RyanG
同感,谢谢分享。刚刚浪费了90分钟的生命。 - Gapp
另一位尴尬的开发者向您表示感谢 :/ - Rog

2
我认为你最喜欢的来源(“3MB,数百万条记录”)的关键是提到的批处理,以及禁用撤消管理器(这也是苹果公司建议的,并且非常重要)。重要的是,此批处理也必须应用于@autoreleasepool。
在第二个for循环中尝试将第二个@autoreleasepool放入代码中。然后调整您的批量大小以进行微调。
在iPad 1上测试了超过500,000条记录。仅JSON字符串的大小就接近40MB。尽管如此,它仍能正常工作,甚至通过一些调整可以获得可接受的速度。在我的测试中,我可以在原始iPad上声明高达70MB的内存。

嗨Mundi,感谢您的回复。您能够使用那么多记录进行测试实际上是一种解脱。我也希望我能做到这一点。如果我在第二个for循环中放置第二个@autoreleasepool,那么在此情况下,保存会在排空池之后发生吗?实际上,我已经使用了两个for循环进行设置,目的是在引用您提到的排空池后进行保存...这个@autoreleasepool不会影响保存吗? - Tamara Bernad
只要你在 @autoreleasepool 内部保存,就应该会有内存改进。请注意,在代码中,您多次在一个 @autoreleasepool 中保存。 - Mundi
如果我没有记错,每个@autoreleasepool中我只保存一次。请注意,内部循环是一个批处理(它还会增加外部循环的索引i),上下文保存、池子排干,在外层循环的下一轮中创建一个新的@autoreleasepool,并且下一个批次将被处理。 - Tamara Bernad
为什么在重置之前要刷新对象? - Mundi
这并不是必需的,因为上下文中的重置已经足够了。这只是为了确保对象被故障化。 - Tamara Bernad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接