内存监控

基础概念和知识

物理内存和虚拟内存

因为CPU的寻址范围是有限的,取决于CPU的地址总线,例如在32位平台下,其寻址范围就是2^ 32即4G。但是如果每次开启一个线程都给4G内存空间,因为物理内存是有限的,会导致内存很快就分完了,这样的话没有分配资源的进程就只能等待已分配内存的进程执行完成才能执行

因此提出了虚拟内存的概念,它利用磁盘空间虚拟出来一块逻辑内存,而这个用于虚拟内存的磁盘空间被称为交换空间,当内存不足时会使用交换分区的内存空间(即将暂时用不到的资源写入交换空间,将物理内存进行释放)

此时我们给一个进程只需要分配4G的虚拟内存空间,它是一个连续的内存空间(虚拟上的)。此时进程要访问内存地址可能需要一下流程:

  1. 访问地址空间上某一地址,需要把地址翻译为实际需要的物理地址
  2. 虽然所有进程共享这一整块物理内存,每个进程只需要把目前需要的虚拟地址映射到物理内存
  3. 进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录
  4. 页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
  5. 当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常
  6. 缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就找一个页覆盖,至于具体覆盖的哪个页,就需要看操作系统的页面置换算法是怎么设计的了

因此进程工作流程为:
当进程创建时,内核会为进程分配4G虚拟内存,当进程还没开始时,这只是内存布局。实际上并不立即把内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射)。这个时候数据和代码还是在磁盘上的。当运行到对应的程序时,进程去寻找页表,发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,于是将磁盘上的数据拷贝到物理内存中。

另外在进程运行过程中,要通过malloc来动态分配内存时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。

可以认为虚拟空间都被映射到了磁盘空间中(事实上也是按需要映射到磁盘空间上,通过mmap,mmap是用来建立虚拟空间和磁盘空间的映射关系的)

iOS内存是怎么工作的

1. iOS怎么监测内存的

iOS会开启一个优先级最高的线程vm_pressure_monitor来监控系统的内存压力情况,并通过一个堆栈来维护所有App的进程。另外,iOS还会维护一个内存快照表,用于保存每个进程内存也的消耗情况。当系统监测发现某个app内存有压力了,就会发出通知,内存有压力的App就会执行代理didReceiveMemoryWarning代理。通过这个代理,可以获得最后一个编写代码释放内存的机会。通过这段代码的执行,可以避免App被系统强杀

2. 为什么iOS上内存占用过大会被系统强杀呢?(在其他系统或者MacOS就不会)

上面提到了交换空间来作为虚拟内存,但是苹果公司考虑到手持设备的存储空间小的问题,并没有使用交换空间,这样虚拟内存就没法记录到外部存储。因此,苹果引入了MemoryStatus机制

MemoryStatus机制的主要思路就是,在iOS系统上弹出尽可能多的内存供当前应用使用。因此其机制就是先强杀后台应用,如果内存还不够就强杀掉当前应用。
MemoryStatus机制,会开启一个memorystatus_jetsam_thread线程。这个线程和内存压力监测线程vm_pressure_monitor没有联系,只负责强杀应用和记录日志,不会发送消息,所以内存压力检测线程无法获取到强杀应用的消息

3. 系统强杀App时,怎么判断优先级
iOS系统内核中有一个数组,专门用于维护线程优先级。优先级规定:内核占用线程的优先级最高,操作系统次之,App的优先级最后。并且前台App的优先级高于后台运次那个优先级;COU使用多的线程优先级降低

iOS系统在因为内存问题强杀掉App之前,至少有6秒钟时间用来做优先级判断。同时来生成JetSamEvent日志

获取内存

获取当前进程占用内存:

struct mach_task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kl = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
    
NSString *memStr;
if (kl == KERN_SUCCESS) {
    memStr = [NSString stringWithFormat:@"used %llu MB \n",info.resident_size / (1024 * 1024)];
}
NSLog(@"%@", memStr);

//
struct mach_task_basic_info {
    mach_vm_size_t  virtual_size;   //虚拟内存      mach_vm_size_t  resident_size;   //物理内存     mach_vm_size_t  resident_size_max;  /* maximum resident memory size (bytes) */
    time_value_t    user_time;          /* total user run time for
                                         *  terminated threads */
    time_value_t    system_time;        /* total system run time for
                                         *  terminated threads */
    policy_t        policy;             /* default policy for new threads */
    integer_t       suspend_count;      /* suspend count for task */
};

这种方法检测到的物理内存值与Xcode或者Instrument值差异较大
为与Xcode的内存值检测相同可以使用task_vm_info中的phys_footprint

    struct task_vm_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kl = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &info, &size);
    NSString *memStr;
    if (kl == KERN_SUCCESS) {
        memStr = [NSString stringWithFormat:@"used %llu MB \n",info.phys_footprint / (1024 * 1024)];
}