2020-03-12 自动释放池到底能不能彻底解决循环过程中内存飙升的问题?

自动释放池到底能不能彻底解决循环过程中内存飙升的问题?(循环这里指得是for循环)

其实这个问题也是,自动释放池,到底什么时候会去自动释放一下临时使用的内存?

在我过往的开发经历中,
我曾经使用过两次自动释放池来解决循环下载大数据或者循环拷贝数据过程导致内存飙升的问题。
自动释放池的解决方案一直无效。无论下载大图片,还是读取本地相册图片来复制,内存总是轻松上升很快。
事后,我的解决方案是: 第一次:懒加载,延迟下载,避免循环下载。
第二次:手动停止复制,给runloop一个休息的时间,让它去“自动”释放内存。

经过我多次测试和调试,我发现我的循环代码中为什么会出现内存飙升并且自动释放池也无法及时释放内存的原因:

循环过程中出现了block 回调

无论是请求网络资源,还是获取本地资源,都会有这个问题。获取数据过程是耗时过程,会异步化处理,然后获取到的数据在block 回调中返回。

循环多次请求获取数据的过程,通过断点跟踪,你会发现循环执行的很快。假如循环1W次,那么1W次循环执行完毕后,runloop才会进入休息态。

紧接着开始执行回调。注意,这里的数据回调会有一万个!
假设每个数据平均为1MB,那么内存里等待执行回调函数与数据,就至少占用内存10000MB,近似10G内存大小。

而我们的自动释放池,只能释放临时变量,循环里面临时变量除了计数索引外基本上没有了。所以在循环里面加的自动释放池,对block捕获的数据完全没有释放效果。

解决方案:将自动释放池放在block 里面,可以释放block里面临时使用的大内存数据。

但是:按照上面内存操作还是会内存飙升。在for循环执行完之后,在block方法开始对data处理之后,内存瞬间(大概过几秒后)降为正常。

其实这种情况很多人都应该遇到过,大量请求,js开发,后台都会遇到大量请求数据资源的情况?他们是在怎么做的呢?然后我读到了下面这段话:

毕竟编程这么多年,我隐约记得PHP里提供有非一次全部加载数据的API,是像处理流媒体那样,随用随取随丢、数据并不会积累在内存的查询方法。经过简单的搜索,果然在官方网站上找到的正确的用法。

这个问题在PHP的官方网站上叫缓冲查询和非缓冲查询(Buffered and Unbuffered queries)。 PHP的查询缺省模式是缓冲模式。也就是说,查询数据结果会一次全部提取到内存里供PHP程序处理。这样给了PHP程序额外的功能,比如说,计算行数,将 指针指向某一行等。更重要的是程序可以对数据集反复进行二次查询和过滤等操作。但这种缓冲查询模式的缺陷就是消耗内存,也就是用空间换速度。

相对的,另外一种PHP查询模式是非缓冲查询,数据库服务器会一条一条的返回数据,而不是一次全部返回,这样的结果就是PHP程序消耗较少的内存,但却增加了数据库服务器的压力,因为数据库会一直等待PHP来取数据,一直到数据全部取完。

很显然,缓冲查询模式适用于小数据量查询,而非缓冲查询适应于大数据量查询。

关键点:不要一次性加载全部数据,像流媒体那样随取随丢,这样的一种数据并不会积累在内存的查询方法。

再回过头,看这个代码,循环多次请求,大量回调数据。这么多数据必须异步处理吧,同步要么多线程CPU吃不消,要么形成死锁。
这就相当于,一次性请求了全部数据。
就算自动释放池,可以快速释放内存,也得等CPU去执行这个block,只要存在待执行的block和数据处理代码,内存就会飙升。

只要存在等待执行的block和数据处理代码,内存就会飙升。

最终解决方案就是:破除循环,随取随丢,用完就释放。

流程图是:
请求1——>block 加大数据data ——> 处理完数据,释放data所占的内存——>开始请求2……知道处理完最后一个请求。

这个解决方案,即使在block里面不加入自动释放池,苹果的ARC的GC机制也会让内存不发生较大的提升。而且没有使用并发,CPU不会超过100%。

总结:基本上在ARC时代,自动释放池的使用意义已经不大了。

你可能感兴趣的:(2020-03-12 自动释放池到底能不能彻底解决循环过程中内存飙升的问题?)