objective-c 实际回收内存资源时间是由系统决定的
来源:互联网 发布:网络歌手好听的歌2017 编辑:程序博客网 时间:2024/06/05 18:44
今天面试的一个有争议话题:面试官说autoreleasepool中的对象在下一次的事件环之前进行内存的回收。这是错误的。
退出autoreleasepool之前,系统只是对所有的对象发送release消息。
内存的实际回收既不是在退出autoreleasepool之后,也不是在下一次事件循环之前。
实际的回收时间是由系统决定的。操作系统根据资源(CPU)使用的状况,绝定是否回收实际的内存。
autoreleasepool退出之前只是将所有对象的引用计数减一。
如果CPU不是很繁忙,系统根据运行状况会在下一次事件环执行前,实际回收内存。
这只是在instrumen工具调试时系统展现出来的性质。
我们现在联机的情况下做如下的测试:
1 执行下面代码,内存持续走高,CPU使用率到了90%。
for (int i= 0; i < 1100; i++) {
[self justAlloc];
}
-(void) justAlloc{
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
for (int i= 0; i < 10; i++) {
ssViewController * sdfsf = [[ssViewController alloc] initWithNibName:@"ssViewController"bundle:nil];
NSLog(@"%@", sdfsf.view);
}
}
});
}
2 执行下面代码,内存基本稳定,CPU使用率在20%以下。
for (int i= 0; i < 1100; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:i/10];
}
3 执行下面代码,内存比2有所增加单基本稳定,上下有所变动,CPU使用率在80%以下。for (int i= 0; i < 1100; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:i/100];
}
for (int i= 0; i < 1100; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:i/100];
}
上面四个实验说明,内存的具体回收是由系统决定的,runloop对回收的时刻是没有绝对的控制的。
只有在系统系统资源不是特别繁忙时,runloop的完当前handler执行完毕时系统会立刻着手于内存资源的回收。
在CPU使用率到90%以上时,可以观察到一个有趣的现象:程序执行完毕,CPU降下来后,系统内存的峰值几乎保存不动了。
下面我们做一个更为极端的测试,来看看为什么这种现象出现,系统到底干了什么?
5 下面代码使内存迅速升到500M产生内存错误!这是block过多导致的。
for (int i= 0; i < 1000*1000; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:0];
}
6下面代码使内存上到550M后,开始出现了磁盘的读写,随后内存下降到500左右。运行一段时间后,也是出现了内存错误。
for (int i= 0; i < 100 *1000; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:0];
}
7 下面代码运行可真长结束,峰值在85M,结束后峰值回到65M左右保持不变。同样,运行结束后并没有出现内存峰值的完美降低。
for (int i= 0; i < 10*1000; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:0];
}
8 下面代码运行可真长结束,峰值在10.4M,结束后峰值回到8.5M左右保持不变。同样,运行结束后并没有出现内存峰值的完美降低。
for (int i= 0; i < 1*1000; i++) {
[self performSelector:@selector(justAlloc) withObject:nil afterDelay:0];
}
上面5中提到block过多直接导致内存错误,以emptyBlock函数替换justAlloc。
5‘,可以发现5还是内存上升到500后产生内存错误。
6’,可视内存升到455M左右,结束运行,并是内存保持在455M,没有任何的降低。粗略计算一下每一个block的代价: (455-3)* 1000 K / (100*1000) = 4.52K。
7‘,可视内存升到50M左右,结束运行,并是内存保持在48.1M,没有任何的降低。粗略计算一下每一个block的代价: (48.1-3) * 1000 K / (10*1000) = 4.51K。
8',以1*1000次循环:结束运行,并是内存保持在3.9M,没有任何的降低。粗略计算一下每一个block的代价:( 7.2-3)* 1000 K / (1*1000) = 4.2K。
-(void) emptyBlock{
dispatch_async(dispatch_get_main_queue(), ^{
});
}
对比 7 7’ : 65 - 48.1 = 7M
对比 8 8’ : 8.5 - 7.2 = 0.7M
7 == 0.7 * 10 发生了什么?是巧合吗?正好是循环相差的次数。
在7中峰值从85降到65M说明系统运行结束后确实进行了内存的回收,但是,去除BLOC的影响,还有每次运行0.7* 1000K/ 1000 = 0.7K的内存空间没有释放。
为了验证消除偶发性,我们现在再去以50*1000次做实验 9 9‘:
9 峰值420M 降低后峰值297.2M, 在7中 85 * 5 = 425M 65*5 = 325M 。基本符合 5 被的关系。
9’ 峰值229.3M , 在7‘ 中 48.1 * 5 = 240.5M 。基本符合 5 被的关系。 block代价: 229.3 M / (50 * 1000) = 4.58 K
分配对象对内存回收后的影响: 297.2M - 229.3M = 68M 7M * 5 = 35 M 出现较大的偏差。
以20*1000次做实验 10 10‘:
10 峰值167M 降低后峰值122.6M, 在7中 85 * 2 = 170M 65*2 = 130M 。基本符合 2 被的关系。
10’ 峰值93.4M , 在7‘ 中 48.1 * 2=96.2M 。基本符合 2 被的关系。 block代价: 93.4M / (20 * 1000) = 4.67 K
分配对象对内存回收后的影响: 122.6M - 93.4M = 29.2M 7M * 2 = 14 M 出现较大的偏差。
以30*1000次做实验 11 11‘:
11 峰值247M 降低后峰值181M, 在7中 85 * 3 = 255M 65*3 = 195M 。基本符合 3 倍的关系。
11’ 峰值138.5M , 在7‘ 中 48.1 * 3=144.3M 。基本符合 3 被的关系。 block代价: 144.3M / (30 * 1000) = 4.81 K
分配对象对内存回收后的影响: 181M - 138.5M = 42.5M 7M * 3 = 21 M 出现较大的偏差。
循环次数 1000 10 * 1000 20 * 1000 30 * 1000 50* 1000 40* 1000
对象影响 0.7 M 7M 29.2 42.5 68 ?
以40*1000次做实验 12 12‘:
12 峰值332M 降低后峰值238.6M, 在7中 85 * 4 = 340M 65*4 = 260M 。基本符合 3 倍的关系。
12’ 峰值183.6M , 在7‘ 中 48.1 * 4=192.4M 。基本符合 3 被的关系。 block代价: 183.6M / (40 * 1000) = 4.59 K
分配对象对内存回收后的影响: 247.3M - 183.6M = 63.7M 7M * 4 = 28 M 出现较大的偏差。
12'' 去掉dispatch 时,对象峰值为 170M , 结束后稳定在78M 。可以看出峰值正好比12少了block的开销63M。
40*1000与50*1000差距非常小,是否是在当前环境下,这是可运行的最坏情况呢?
当取70*1000 时
13 85 * 7 = 595M 达到了600 估计会崩溃。但实验表明运行中内存稳定在460M的峰值以下,并同时出现了磁盘的读写,结束后下降到了350M的。
可知当系统太忙了同时可用内存将好近时,系统放弃了回收内存的实际操作,而选择了虚拟内存机制。
13‘ 峰值为319.4 block代价: 319.4 / (70 * 1000) = 4.57 K
当取60*1000 时
14 85 * 6 = 510M 在崩溃好近内存边缘。但实验表明运行中到达峰值在490M时下,出现了磁盘的读写,结束后下降到了350M。
14‘ 峰值为274.2 block代价: 274.2 / (60 * 1000) = 4.57 K
以上数据是连接调试的结果。联调会有很多附加的性能要求和资源要求。
在使用instruments时,有可能得到不同的数据但是趋势是基本一致的。
在下一篇文章中我会给出使用instruments时的实验数据及其分析。同样instruments的使用也会很大的耗费资源。
1 oc中的对象释放与内存的回收是两回事。
2 autoreleasepool实际回收内存资源时间是由系统决定的。NSRunLoop当前事件执行完毕,下一次事件开始之前并不一定去做实际的内存回收操作。
3 block线程中放入过多的block,在内存耗尽是会产生error,而不会启用虚拟内存机制。
4 当其他已释放对象过多而耗尽系统内存时,当CPU过于忙碌,系统会启用虚拟内存机制。
5 很有可能内存回收机制要比虚拟内存复杂的多,费时的多。
6 向队列dispatch block的代价是很高的。4.57 K的内存损耗。
使用instrument下实验
-(void) justBlock{
dispatch_async(dispatch_get_main_queue(), ^{
});
}
-(void) justAlloc{
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
for (int i=0; i <10; i++) {
@autoreleasepool {
ssViewController * sdfsf = [[ssViewController alloc] initWithNibName:@"ssViewController" bundle:nil];
NSLog(@"%@", sdfsf.view);
}}
}
});
}
15 内存升到600M崩溃,persist对象的数量达到9,919,313个,而transient对象的数量只有4037个。被释放对象的内存并没有被回收!
for (i= 0; i < 1000 * 1000 * 1000; i++) {
[self justBlock];
}
16 内存峰值为60M,其中VM很小,多次运行显示,有时在运行期间transient对象的数量有所增加,在运行结束后能够对已释放的对象进行干净的内存回收。
for (i= 0; i < 1000 * 1000; i++) {
[self justBlock];
}
17修改justBlock 峰值123M , persist object 在长时间内存维持在100M左右,transient对象数量持续增加。
最后persist 对象占用内存在17.3稳定运行结束。
同样,在内存达到峰值前,transient对象数据几乎不变。
我们看到,加入内存使用代码对性能的巨大影响。这是科学方法论中有很深刻的讨论。
同时,联机调试反应出来的机制与instrument的是一致的:系统繁忙时,直到到达峰值前,不会进行实际内存的回收。
甚至autorelease 尾部的减一操作,在何时执行的我们也是能确定的,但是这在程序内是无法测量的。我们只知道,在语法上,它在尾部执行了减一操作。
-(void) justAlloc{
__weaktypeof(self) wself =self;
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
for (int i=0; i <10; i++) {
@autoreleasepool {
}}
}
double freeM = [wselfavailableMemory];
double usedM = [wselfusedMemory];
if (usedM > wself.usedM) {
wself.usedM = usedM;
wself.freeM = freeM;
}
wself.freeLbl.text = [NSStringstringWithFormat:@"%d M", (int)wself.freeM];
wself.usedLbl.text = [NSStringstringWithFormat:@"%d M", (int)wself.usedM];
});
}
// 获取当前设备可用内存(单位:MB)
- (double)availableMemory
{
vm_statistics_data_t vmStats;
mach_msg_type_number_t infoCount =HOST_VM_INFO_COUNT;
kern_return_t kernReturn =host_statistics(mach_host_self(),
HOST_VM_INFO,
(host_info_t)&vmStats,
&infoCount);
if (kernReturn !=KERN_SUCCESS) {
returnNSNotFound;
}
return ((vm_page_size *vmStats.free_count) /1024.0) / 1024.0;
}
// 获取当前任务所占用的内存(单位:MB)
- (double)usedMemory
{
task_basic_info_data_t taskInfo;
mach_msg_type_number_t infoCount =TASK_BASIC_INFO_COUNT;
kern_return_t kernReturn =task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&taskInfo,
&infoCount);
if (kernReturn !=KERN_SUCCESS
) {
returnNSNotFound;
}
return taskInfo.resident_size /1024.0 /1024.0;
}
断开instrument,直接在机器上测试: 执行完毕驻留内存达到131M 。
现在,CPU闲了下来,但系统仍没有做内存的回收。
只有在使用instrument的情况下,系统才能做到在不忙的时候去回收内存。
要认清内存的释放、回收与objective-c中的引用计数清零的区别。
内存的事向来都是操作系统的事。应用层只是告诉操作系统是需要新的资源,还是使用完毕你可以拿回去了。
至于何时做、整么做,对于引用计算范畴内的应用是透明的。
现在又出现一个问题,在非引用计算范畴内是什么情况呢?
下面我们将来一起看一看 free malloc 。
安照上面的实验与论述,其结果应该是一样的,下图说明了这一点。
无论应用以何种形式进行内存的请求,在那个层次的请求,应用对于内存的释放或是引入计数清理,于操作系统来说只是一个通知,系统对外的接口是统一的。
具体回不回收声明释放的内存,何时回收,是系统决定的。
但是当内存升到500M时,产生内存错误,与block一致,不启动虚拟内存机制。
for (long i=0; i < VeryBig; i++) {
[self justAlloc];
}
-(void) justAlloc{
dispatch_async(dispatch_get_main_queue(), ^{
for (int i=0; i <10; i++) {
char * ch =malloc(1024);
ch[0] = i;
free(ch);
}
});
}
说应用层的释放请求,确保了应用内存峰值点,是即其肤浅的说法。
尤其在IOS的引用计数下,说什么引用计数清理了就是释放内存了更是不稳妥的说法。
因为在引用计数的下面的某层一定曾在对free的调用。而具体free的执行时机我们又是不得而知了。
引用计数的清零只是使应用的行为满足了操作系统对其的限制、符合了编程语言在语法和语义是的要求。
但是,不要去问别人什么时候进行内存回收(释放),尤其是在讨论完了引用计算什么 时候清零再去问问内存释放的时间。
结论:
1 对象(内存)的释放与内存的回收是两回事。释放是应用的愿望,回收方式、时间是由系统决定的。
2 autoreleasepool实际回收内存资源时间是由系统决定的。NSRunLoop当前事件执行完毕,下一次事件开始之前系统并不一定去做实际的内存回收操作。
3 dispatch放入过多的block 或是在其中malloc free过多的内存,在内存耗尽时会产生内存error,而不会启用虚拟内存机制。
4 当其他已释放对象过多而耗尽一定数量的系统内存时,当CPU过于忙碌,系统会启用虚拟内存机制。
5 这样看来虚拟内存机制要比内存回收机制占用较少的系统资源。
后记: 里面一定会存在错误或是误解,欢迎大家指出了。同时排版很乱,大家将就看吧!
- objective-c 实际回收内存资源时间是由系统决定的
- 好的系统是由意识、想法决定,而不是由语言决定
- 决定实际系统的ERP软件
- 收入是由什么决定的
- Objective-C中的内存回收机制简介
- C语言中的int类型的范围是由什么决定的
- C语言中的int类型的范围是由什么决定的
- C语言中的int类型的范围是由什么决定的
- 生产消费者4 - 实现一个基于优先级的传输队列【消费顺序是由优先级决定的而不是抵达时间】
- 航天飞机的宽度是由马屁股决定的
- 航天飞机的宽度是由马屁股决定的
- 航天飞机的宽度是由马屁股决定的
- 航天飞机的宽度是由马屁股决定的
- 今天的生活是由3年前决定的
- 485的A、B端电压是由什么决定的?
- 虚拟物品价格是由什么决定的
- 指针的大小到底是由谁决定?是多少?
- 指针的大小到底是由谁决定?是多少?
- Java多线程/并发19、Callable、Future接口及CompletionService的应用
- OpenCV之Mat——合并多个矩阵
- 第三章、如何运行程序
- Python处理异常
- 《Unity3d脚本编程 使用C#语言开发跨平台游戏》读书笔记2
- objective-c 实际回收内存资源时间是由系统决定的
- 使用jquery控制display属性
- 我的mysql知识目录
- JAVA视频网盘分享
- LeetCode 2. Add Two Numbers
- Spring之对象依赖关系
- java实现动态验证码源代码——绘制验证码的jsp
- 详解事务的隔离级别
- JavaScript优质文章汇总