iOS进程发生OOM时,内存使用情况是怎么样的,如果想要获取确切的内存使用情况应该从何下手,带着这个问题,我开始了OOM的探险之旅🐕
如何获取OOM发生时的内存阈值
「Jetsam event reports describe the system-memory conditions under which the operating system terminated an app.」#
当系统内存不足时,内核中的内存检测进程会将当前优先级不高或是内存消耗较多的行程杀掉,这种机制称为jetsam机制#,当进程被Jetsam机制杀死时,会在手机中生成以JetSamEvent开头的系统日志,保存在『设置->隐私->分析与改进->分析数据』中。
通过Jetsam日志计算得到
Jetsam 事件报告不包含应用程序中执行线程的堆栈跟踪,但它们确实包含有关内存使用的系统信息。
{"bug_type":"298","timestamp":"2021-12-29 15:08:06.53 +0800","os_version":"iPhone OS 15.2 (19C56)","incident_id":"490A68C6-6393-4DEA-B4CC-821E15266F7A"}
{
"crashReporterKey" : "feb8d748453bb378582d9050353efad03cad94c8",
"kernel" : "Darwin Kernel Version 21.2.0: Sun Nov 28 20:43:34 PST 2021; root:xnu-8019.62.2~1\/RELEASE_ARM64_T8010",
"product" : "iPhone9,2",
"incident" : "490A68C6-6393-4DEA-B4CC-821E15266F7A",
"date" : "2021-12-29 15:08:06.53 +0800",
"build" : "iPhone OS 15.2 (19C56)",
"timeDelta" : 8,
"memoryStatus" : {
"compressorSize" : 80202,
"compressions" : 34418947,
"decompressions" : 24922483,
"zoneMapCap" : 1130594304,
"largestZone" : "APFS_4K_OBJS",
"largestZoneSize" : 39010304,
"pageSize" : 16384,
"uncompressed" : 222879,
"zoneMapSize" : 185417728,
"memoryPages" : {
"active" : 32308,
"throttled" : 0,
"fileBacked" : 23659,
"wired" : 34581,
"anonymous" : 40800,
"purgeable" : 0,
"inactive" : 29307,
"free" : 5793,
"speculative" : 2844
}
},
"largestProcess" : "iQiYiPhoneVideo",
"genCounter" : 1,
"processes" : [
...
{
"uuid" : "ac79b01e-f7c7-309d-a851-66ace54e65f7",
"states" : [
"suspended"
],
"purgeable" : 0,
"age" : 10767795775,
"fds" : 25,
"coalition" : 68,
"rpages" : 122,
"priority" : 0,
"physicalPages" : {
"internal" : [
4,
112
]
},
"freeze_skip_reason:" : "out-of-slots",
"pid" : 10152,
"cpuTime" : 0.069683999999999996,
"name" : "QiYiUserNotification",
"lifetimeMax" : 124
},
...
{
"uuid" : "386d99ce-bb27-39a8-bf63-7e546ef31a0d",
"states" : [
"frontmost"
],
"purgeable" : 0,
"age" : 1682110655,
"fds" : 200,
"coalition" : 4251,
"rpages" : 30002,
"priority" : 10,
"physicalPages" : {
"internal" : [
15757,
12812
]
},
"freeze_skip_reason:" : "out-of-slots",
"pid" : 10159,
"cpuTime" : 61.119537000000001,
"name" : "iQiYiPhoneVideo",
"lifetimeMax" : 31808
},
...
上图是截取的日志部分内容,先解读下关键信息
- processes
jetsam 事件报告包含一个processes数组,数组中的每个项目描述系统中的单个进程。 - pageSize
pageSize:指的是当前设备物理内存页的大小,当前设备是iPhone 7 Plus,大小是 16KB,苹果 A7 芯片之前的设备物理内存页大小则是 4KB。 - states
「Describes the app’s current memory use state, such as using memory as the frontmost app, or suspended and not actively using memory」#
表示当前进程内存消耗时在操作系统中的运行状态(idle、frontmost、suspended、resume) - rpages
resident pages表示进程占用的内存页数量 - lifetimeMax
「 The highest number of memory pages allocated during the lifetime of the process.」#
进程生命周期内分配的最大内存页数。 - name
进程名称 - reason
「A jetsam event report contains a processes array, with each item in the array describing a single process in the system. Search for the reason key to identify the jettisoned process and why the system jettisoned it. Only the jettisoned process has the reason key.」#
搜索reasonJetsam_exit_reason_definitions-related_to_memorystatus以识别被抛弃的进程以及系统为何抛弃它。只有被抛弃的进程才有reason。另外上面这个日志并不存在reason字段,但是可以在别的日志中找到。
由于Jetsam机制本质是出于内存监控和保护,所以内存不足时就有可能会触发jetsam(咦,我为什么说是可能?先想一想,再往下看😁)。而导致内存不足的原因可能是其他进程,也可能是当前进程,这两种情况都会触发 Jetsam强杀进程,并且有可能是强杀N个进程。明白了这个背景原因,那么有些文章分析通过「pageSize * rpage」得到的内存值仅代表发生OOM时进程已经使用了这么多的内存大小,并不能确信的表示为进程可用内存的上限。
通过主动触发OOM
如果进程持续申请内存,且缓慢上涨,导到hightwatermask临界值,会触发Jetsam,代码如下。
+ (void)foo {
while (1) {
for (int i = 0; i < 10; i++) {
UIImage *image = [UIImage imageNamed:@"testImage"];
[self.datas addObject:image];
}
[self footprintMemory];
[self limitMemory];
}
}
+ (long)footprintMemory
{
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
if (result != KERN_SUCCESS)
return 0;
return (long long)(vmInfo.phys_footprint);
}
- (long)limitMemory
{
if (@available(iOS 13.0, *))
{
long availMem = os_proc_available_memory();
}
return 0;
}
task_info
#
通过第二个参数flavor获取信息,可用的信息有flavor
os_proc_available_memory
#
通过vmInfo.phys_footprint
、os_proc_available_memory()
获得已用内存以及可用内存的情况,两者的和就是触发内存的上限。
文内链接
简书不支持锚点链接,所有404的链接都在这里。#表示外部超链接,可以正常访问。
Jetsam exit reason definitions - related to memorystatus#
下面列表的声明出现在Darwin xnu-7195.101.1#版本。
/*
* Jetsam exit reason definitions - related to memorystatus
*
* When adding new exit reasons also update:
* JETSAM_REASON_MEMORYSTATUS_MAX
* kMemorystatusKilled... Cause enum
* memorystatus_kill_cause_name[]
*/
#define JETSAM_REASON_INVALID 0
#define JETSAM_REASON_GENERIC 1
#define JETSAM_REASON_MEMORY_HIGHWATER 2
#define JETSAM_REASON_VNODE 3
#define JETSAM_REASON_MEMORY_VMPAGESHORTAGE 4
#define JETSAM_REASON_MEMORY_PROCTHRASHING 5
#define JETSAM_REASON_MEMORY_FCTHRASHING 6
#define JETSAM_REASON_MEMORY_PERPROCESSLIMIT 7
#define JETSAM_REASON_MEMORY_DISK_SPACE_SHORTAGE 8
#define JETSAM_REASON_MEMORY_IDLE_EXIT 9
#define JETSAM_REASON_ZONE_MAP_EXHAUSTION 10
#define JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING 11
#define JETSAM_REASON_MEMORY_VMCOMPRESSOR_SPACE_SHORTAGE 12
#define JETSAM_REASON_LOWSWAP 13
#define JETSAM_REASON_MEMORYSTATUS_MAX JETSAM_REASON_LOWSWAP
/*
* Jetsam exit reason definitions - not related to memorystatus
*/
#define JETSAM_REASON_CPULIMIT 100
flavor
每个flavor后面都跟着一个结构体,从结构体可以获取有用信息
...
#define MACH_TASK_BASIC_INFO 20 /* always 64-bit basic info */
---
#define TASK_POWER_INFO 21
---
#define TASK_VM_INFO 22
#define TASK_VM_INFO_PURGEABLE 23
struct task_vm_info {
mach_vm_size_t virtual_size; /* virtual memory size (bytes) */
integer_t region_count; /* number of memory regions */
integer_t page_size;
mach_vm_size_t resident_size; /* resident memory size (bytes) */
mach_vm_size_t resident_size_peak; /* peak resident size (bytes) */
mach_vm_size_t device;
mach_vm_size_t device_peak;
mach_vm_size_t internal;
mach_vm_size_t internal_peak;
mach_vm_size_t external;
mach_vm_size_t external_peak;
mach_vm_size_t reusable;
mach_vm_size_t reusable_peak;
mach_vm_size_t purgeable_volatile_pmap;
mach_vm_size_t purgeable_volatile_resident;
mach_vm_size_t purgeable_volatile_virtual;
mach_vm_size_t compressed;
mach_vm_size_t compressed_peak;
mach_vm_size_t compressed_lifetime;
/* added for rev1 */
mach_vm_size_t phys_footprint;
/* added for rev2 */
mach_vm_address_t min_address;
mach_vm_address_t max_address;
/* added for rev3 */
int64_t ledger_phys_footprint_peak;
int64_t ledger_purgeable_nonvolatile;
int64_t ledger_purgeable_novolatile_compressed;
int64_t ledger_purgeable_volatile;
int64_t ledger_purgeable_volatile_compressed;
int64_t ledger_tag_network_nonvolatile;
int64_t ledger_tag_network_nonvolatile_compressed;
int64_t ledger_tag_network_volatile;
int64_t ledger_tag_network_volatile_compressed;
int64_t ledger_tag_media_footprint;
int64_t ledger_tag_media_footprint_compressed;
int64_t ledger_tag_media_nofootprint;
int64_t ledger_tag_media_nofootprint_compressed;
int64_t ledger_tag_graphics_footprint;
int64_t ledger_tag_graphics_footprint_compressed;
int64_t ledger_tag_graphics_nofootprint;
int64_t ledger_tag_graphics_nofootprint_compressed;
int64_t ledger_tag_neural_footprint;
int64_t ledger_tag_neural_footprint_compressed;
int64_t ledger_tag_neural_nofootprint;
int64_t ledger_tag_neural_nofootprint_compressed;
/* added for rev4 */
uint64_t limit_bytes_remaining;
/* added for rev5 */
integer_t decompressions;
};
...
网友评论