0%

VM: CG raster data 崩溃探究

文长不看

  1. 分析内存虚拟内存和堆内存(All Heap Allocagtions)都需要关注, VM 也是有区别的,有些会导致 OOM
  2. VM: CG raster data 会导致崩溃
  3. 创建22小位图,Instruments 标记的是堆内存,100100 的位图,Instruments 标记的是 VM: CG raster data
  4. AutoLayout 用多了也要占用内存,CollectionCell 上最好都使用手动布局(不光内存更优,性能也更好)
  5. 屏幕中 Cell 数量越多,占用内存越多。
  6. Cell 越复杂,占用的内存越多

背景

有人反馈了打开一个面板内存上涨了 150m。instruments 分析以后得到如下数据:

150 M 其中大头都是 VM 内存, 勾选只看堆内存(All Heap Allocagtions) 创建的内存为 46M

30.88 MB 66.3% 262648 start

8.96 MB 19.2% 2845 thread_start

6.73 MB 14.4% 55930 start_wqthread

VM 内存会把机器搞挂么?

Case 1

我写了个 demo, 设置 kCGImageSourceShouldCacheImmediately = YES, kCGImageSourceShouldCache = YES。

在 iPad pro 上暴力测试,VM 上涨到 70 G (有点心疼我自己的机器,没有继续测试了),进程也一直没有挂。

Case 2

但是如果不断调用 UIGraphicsBeginImageContextWithOptions 创建位图并且不释放, CG raster data vm bu

Case 3

malloc 分配 堆内存 2.8G 时 OOM

这里我们得出结论:分析内存虚拟内存和堆内存(All Heap Allocagtions)都需要关注, VM部分也是有区别的,有些会导致 OOM,有些不会

Case 4

创建2*2小位图,Instruments 标记的是堆内存

1
2
3
4
5
6
7
8
- (void)testCGRasterDataOOM {
// 小图用的是堆内存
UIGraphicsBeginImageContextWithOptions(CGSizeMake(2, 2), NO, 1);
NSMutableArray *arr = [NSMutableArray new];
while (1) {
[arr addObject:UIGraphicsGetImageFromCurrentImageContext()];
}
}

Case 5

创建 100*100 的位图,Instruments 标记的是 VM: CG raster data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_imageView];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1);
[[UIColor redColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000));
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *data = UIImageJPEGRepresentation(img, 0.9);

NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
[data writeToFile:cachePath atomically:YES];

while (1) {
@autoreleasepool {
[self.class loadCachedImageWithoutDecode];
}
}
}

+ (nullable UIImage *)loadCachedImageWithoutDecode {

NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {

NSURL *imageUrl = [NSURL fileURLWithPath:cachePath];
NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO ,
(__bridge id)kCGImageSourceShouldCacheImmediately : @NO};
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions);
if (imageSource) {
NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @YES ,
(__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG,
(__bridge id)kCGImageSourceShouldCacheImmediately : @YES};
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
CFRelease(imageSource);

if (imageRef) {
return [UIImage imageWithCGImage:imageRef];
}
}
}
return nil;
}

AutoLayout 也会是吃内存

一个奇怪的问题:AutoLayout 也会是吃内存的,特别是约束特别多的时候(贴纸面板的这个场景一口气加载了十几页的数据,创建了非常多的 Cell。

删除了约束改成手写 frame ,删除无用的控件以后,内存下降了14 M。

优化 6.73M 解码后的图片

图片缓存的问题:只在必要的时候解码,贴纸的场景,在绘制完本地的贴纸(因为有时间、用户名信息,需要经常重绘,比较难复用),可以通过创建完之后先写入磁盘,生成一个没有解码的 UIImage来减少内存的峰值

可以利用 imageIO 实现一个 loadCachedImageWithoutDecode,也可以直接用 [UIImage imageWithData:data] 来实现一样的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_imageView];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1);
[[UIColor redColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000));
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *data = UIImageJPEGRepresentation(img, 0.9);

NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
[data writeToFile:cachePath atomically:YES];

// self.image = [self.class loadCachedImageWithoutDecode];
// self.image = img;
// self.image = [UIImage imageWithData:data];
}

+ (nullable UIImage *)loadCachedImageWithoutDecode {

NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {

NSURL *imageUrl = [NSURL fileURLWithPath:cachePath];
NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO ,
(__bridge id)kCGImageSourceShouldCacheImmediately : @NO};
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions);
if (imageSource) {
NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @NO ,
(__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG,
(__bridge id)kCGImageSourceShouldCacheImmediately : @NO};
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
CFRelease(imageSource);

if (imageRef) {
return [UIImage imageWithCGImage:imageRef];
}
}
}
return nil;
}

【10 屏消耗内存】

20*20 50*50 100*100
17 m 4.6m 1.66m
贴一个 imageView 20.9m 5.5m 1.97m
贴一个 UIView 20.5m 5.4m 1.96m

本文测试使用的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>

@interface ViewController (){
int allocatedMB;
Byte *p[10000];
}
@property (nonatomic) UIImage *image;
@property (nonatomic) UIImageView *imageView;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_imageView];

// UIImage *img = [self createDecodedImage];

// NSData *data = UIImageJPEGRepresentation(img, 0.9);
//
// NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
// [data writeToFile:cachePath atomically:YES];
//
// self.image = [self.class loadCachedImageWithoutDecode];
//// self.image = img;
//// self.image = [UIImage imageWithData:data];
// [self.class loadCachedImageWithoutDecode];
[self testCGRasterDataOOM];
}

- (void)testHeepOOM {
while (1) {
[NSThread sleepForTimeInterval:0.1];
[self allocateMemory];
}
}

- (void)testImageNoReleaseOOM {
NSMutableArray *arr = [NSMutableArray new];
while (1) {
[NSThread sleepForTimeInterval:0.1];
[arr addObject:[self createDecodedImage]];
}
}

- (void)testCGRasterDataOOMSmallSize {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(2, 2), NO, 1);
NSMutableArray *arr = [NSMutableArray new];
while (1) {
[arr addObject:UIGraphicsGetImageFromCurrentImageContext()];
}
}

- (void)testCGRasterDataOOM {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 1);
NSMutableArray *arr = [NSMutableArray new];
while (1) {
[NSThread sleepForTimeInterval:0.01];
[arr addObject:UIGraphicsGetImageFromCurrentImageContext()];
}
}

- (void)allocateMemory {
p[allocatedMB] = malloc(10485760);
memset(p[allocatedMB], 0, 10485760);
allocatedMB += 1;
}

- (UIImage *)createDecodedImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1);
[[UIColor redColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000));
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
img = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();
return img;
}

+ (nullable UIImage *)loadCachedImageWithoutDecode {
NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {

NSURL *imageUrl = [NSURL fileURLWithPath:cachePath];
NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO ,
(__bridge id)kCGImageSourceShouldCacheImmediately : @NO};
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions);
if (imageSource) {
NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @YES ,
(__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG,
(__bridge id)kCGImageSourceShouldCacheImmediately : @YES};
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
CFRelease(imageSource);

if (imageRef) {
return [UIImage imageWithCGImage:imageRef];
}
}
}
return nil;
}

@end

相关文档

ios app maximum memory budget

https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html

https://www.jianshu.com/p/1f884615a1d7?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

https://blog.jamchenjun.com/2018/08/22/image-and-graphics-best-practices.html