百度APP iOS端内存优化-原理篇

13,408 阅读14分钟

图片

一、Mach虚拟内存

1.1 Mach内存简介

iOS系统架构可分为内核驱动层(Kernel and Device Drivers Layer)、核心操作系统层(Core OS )、核心服务层(Core Services layer)、媒体层(Media layer可触摸层&应用层(Cocoa&Application layer),内核驱动层就是我们经常提到的Darwin,Darwin是苹果公司于2000年发布的一个开源操作系统,是由XNU和一些其他的Darwin库组成,XNU是由苹果公司发布的操作系统内核,XNU包含三部分:Mach、BSD、I/O Kit。

图片

Mach是一个由卡内基梅隆大学开发的计算机操作系统微内核,是XNU内核,是作为 UNIX 内核的替代,主要解决 UNIX 一切皆文件导致抽象机制不足的问题,为现代操作系统做了进一步的抽象工作。Mach 负责操作系统最基本的工作,包括进程和线程抽象、处理器调度、进程间通信、消息机制、虚拟内存管理、内存保护等。在iOS系统架构中,内存管理是由在Mach层中进行的,BSD只是对Mach接口进行了POSIX封装,方便用户态进程调用。

1.2 Mach虚拟内存的特点

1.2.1 虚拟段页式内存管理

页是内存管理的基本单位, 在 Intel 和 ARM 中,通常为4K,常用的查看虚拟内存的命令:hw.pagesize 查看默认页面大小; vm_page_free_count:当前空闲的 RAM 页数;vm_stat(1) - 从系统范围的角度提供有关虚拟内存的统计信息。 图片 在 iOS ARM64机型中page size是16K,在 JetsamEvent 开头的系统日志里pageSize 代表当前设备物理内存页的大小。 图片

1.2.2 iOS系统没有交换空间

手机自带的磁盘空间也很小,属于珍贵资源,同时跟桌面硬件比起来,手机的闪存 I/O 速度太慢,所以iOS系统没有交换空间;对于Mac系统,参考 Apple 官方文档About the Virtual Memory System,Mac 上有交换空间有换页行为,也就是当物理内存不够了,就把不活跃的内存页暂存到磁盘上,以此换取更多的内存空间。

1.2.3 内存压缩技术

内存压缩技术是从 OS X Mavericks (10.9) 开始引入的 (iOS 则是 iOS 7.0 开始),可以参考官方文档: OS X Mavericks Core Technology Overview, 在内存紧张时能够将最近使用过的内存占用压缩至原有大小的一半以下,并且能够在需要时解压复用。简单理解为系统会在内存紧张的时候寻找 inactive memory pages 然后开始压缩,达到释放内存的效果,以 CPU 时间来换取内存空间,NSPurgeableData是使用该技术典型的数据结构。所以衡量内存指标一定要记录 compressed内存 ,另外还需要记录被压缩的 page 的信息。

1.2.4 内存报警

经过前面的内存压缩环节后,设备可用内存若仍处于危险状态,iOS系统需要各个App进程配合处理,会向各进程发送内存报警要求配合释放内存,具体来说,Mach内核系统的vm_pageout 守护程序会查询进程列表及其驻留页面数,向驻留页面数最高的进程发送NOTE_VM_PRESSURE ,被选中的进程会响应这个压力通知,实际表现就是APP收到系统的didReceiveMemoryWarning 内存报警,释放部分内存以达到降低手机内存负载的目标。

在收到内存报警时,App降低内存负载,可以在很大程度上避免出现OOM,具体源码分析见第三节。

1.2.5 Jetsam机制

当进程不能通过释放内存缓解内存压力时,Jestam机制开始介入,这是iOS 实现的一个低内存清理的处理机制,也称为MemoryStatus,这个机制有点类似于Linux的“Out-of-Memory”杀手,最初的目的就是杀掉消耗太多内存的进程,这个机制只有在iOS系统有,在Mac系统是没有的。系统在强杀 App 前,会先做优先级判断,那么,这个优先级判断的依据是什么呢?

iOS 系统内核里有一个数组,专门用于维护线程的优先级。这个优先级规定就是:内核线程的优先级是最高的,操作系统的优先级其次,App 的优先级排在最后,并且,前台 App 程序的优先级是高于后台运行 App 的,线程使用优先级时,CPU 占用多的线程的优先级会被降低。

1.3 Mach内存管理数据结构

Mach虚拟内存这一层完全以一种机器无关的方式来管理虚拟内存,这一层通过vm_map、vm_map_entry、vm_objec和vm_page四种关键的数据结构来管理虚拟内存。

第一、vm_map:表示地址空间内的多个虚拟内存区域。每一个区域都由一个独立的条目vm_map_entry表示,这些条目通过一个双向链表vm map links维护,参考XNU开源代码(opensource.apple.com/source/xnu/…

第二、vm_map_entry:这个数据结构向上承接了vm_map,向下指向vm_object,该数据结构有很多权限访问标志位,任何一个vm_map entry都表示了虚拟内存中一块连续的区域,每一个这样的区域都可以通过指定的访问保护权限进行保护,在XNU源代码路径(osftnk/vm/vm_map.h)可看到具体数据结构定义。

第三、vm_object:这是一个核心数据结构,将前面介绍的vm_map_entry与实际内存相关联,该数据结构主要包含一个vm_page的链表、memory object分页器、标志位(用来表示底层的内存状态如初始化、已创建、已就绪或pageout等状态)和一些计数器(引用计数、驻留计数和联动计数等),XNU源代码路径:osfmk/vm/vm_object.h;

第四、vm_page: 重点包含offset偏移量和很多状态位:驻留内存、正在清理、交换出、加密、重写、和脏等,XNU源代码路径(osftnk/vm/vm_page.h)。

1.4 Mach内核提供的内存操作接口

XNU内存管理的核心机制是虚拟内存管理,在Mach 层中进行的,Mach 控制了分页器,并且提供了各种 vm_ 和 mach_vm_ 消息接口。

Mach内核是按照page size大小来分配的内存的,对于苹果的arm64机型来说的page size是16K大小,但是我们通常在应用程序中在堆上申请内存的时候,单位都是字节,很显然内核提供的函数不适合直接提供给上层使用,这儿存在一个GAP,在iOS系统中libsystem_malloc.dylib就是用来弥补GAP的。

libsystem_malloc.dylib是iOS内核之外的一个内存库,开源地址:opensource.apple.com/source/libm… alloc],或释放对象调用release方法时,请求先会走到libsystem_malloc.dylib的malloc()和free()函数,然后libsystem_malloc会向iOS的系统内核发起内存申请或释放内存,具体来说就是调用操作系统Mach内核提供的内存分配接口去分配内存或释放内存,苹果操作系统Mach内核提供了如下内存操作的相关接口。

函数名说明
mach_vm_allocateallocates "zero fill" memory in the specfied map
mach_vm_deallocatedeallocates the specified range of addresses in the specified address map
mach_vm_protectsets the protection of the specified range in the specified map
mach_vm_mapmaps a memory object to a task’s address space
mach_vm_page_queryquery page infomation

libsystem_malloc就是通过mach_vm_allocate和mach_vm_map来申请page size整数倍大小的内存,然后缓存这些内存页,形成一个内存池。当malloc调用的时候,可以根据传入的size大小来应用不同的分配策略,从这些缓存的内存中,分配一个size大小的内存地址返回给上层调用,同时记录这次分配操作的元数据。当调用free的时候,可以根据分配的地址,找到元数据,进而回收分配的内存。

二、内存分配函数alloc源码分析

为了了解内存分配底层原理,我们从alloc函数源码分析说起,下载objc开源库,然后从iOS做内存分配的函数[NSObject alloc] 开始一起分析。

2.1 objc_rootAlloc函数

+ (id)alloc {
    return _objc_rootAlloc(self);
}
id  _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

调用函数callAlloc,并传入两个值checkNil为false以及allocWithZone为true。

2.2 callAlloc函数

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
   /* 省略 */ 
}

首先_OBJC2_宏定义,代表objc的版本,现在编译器使用的都是Objective-C2.0,进入if语句,slowpath(告诉编译器,传入的条件结果为假的可能性很大),因为objc_rootAlloc传入的checkNil为false,所以不会返回nil,接着执行fastpath(告诉编译器,传入的条件结果为真的可能性很大), 这个判断就是去检测传入的这个类是否实现了allocWithZone方法, 如果没有实现进入下一个函数。

2.3 objc_rootAllocWithZone函数

调用_class_createInstanceFromZone

NEVER_INLINE id  _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

2.4 class_createInstanceFromZone核心函数

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{    //断言机制,防止类并发创建
    ASSERT(cls->isRealized());
    //读取类的标志位,加速类对象的创建
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    // 计算内存空间大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
     /* 省略 */ 
}

我们可以看到先调用instanceSize函数计算出创建对象需要的内存空间大小,然后再调用malloc_zone_calloc或者calloc去分配内存空间。

2.5 instanceSize计算内存空间大小

inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }
    size_t size = alignedInstanceSize() + extraBytes;
    if (size < 16) size = 16;
    return size;
}

为了减少计算时间,先判断缓存是否有值,如果有先从缓存取值,否则需要进入计算逻辑,从上面的代码逻辑中我们看到入参extraBytes值为0,返回值就是alignedInstanceSize,源码如下:

uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}

我们知道OC类结构中,data字段存储类相关信息,其中ro数据结构存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,所以从当前对象所属类的ro中获取instanceSize代表了分配对象所需内存空间。

#   define WORD_MASK 7UL   
static inline uint32_t word_align(uint32_t x) {  
    return (x + WORD_MASK) & ~WORD_MASK;  
}

接下来调用word_align做内存对齐操作,从上述源码可以发现类对象的创建是按16字节对齐,不足16字节的返回16,这是iOS在堆上分配OC对象基本原则,都是以16倍数做分配。

2.6 malloc_zone_calloc函数

malloc_zone_calloc或者calloc去分配内存空间,这两个函数正是libmalloc中重要的内存分配函数,libmalloc库中路径src/malloc可以看到源码

opensource.apple.com/source/libm…

void *
calloc(size_t num_items, size_t size)
{
  return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

MALLOC_NOINLINE
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
    malloc_zone_options_t mzo)
{
  MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

  void *ptr;
  if (malloc_check_start) {
    internal_check();
  }

  ptr = zone->calloc(zone, num_items, size);

  if (os_unlikely(malloc_logger)) {
    malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
        (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
  }

  MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
  if (os_unlikely(ptr == NULL)) {
    malloc_set_errno_fast(mzo, ENOMEM);
  }
  return ptr;
}

void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
  return _malloc_zone_calloc(zone, num_items, size, MZ_NONE);
}

三、内存报警源码分析

3.1 总体流程图

图片

3.2 系统启动初始化

mach系统启动后先做一系列内核初始化工作,函数调用路径为arm_init->machine_startup->kernel_bootstrap->kernel_bootstrap_thread,arm_init函数,XNU代码路径:/osfmk/arm/arm_init.c

void arm_init( boot_args       *args)
{ /* 省略 */ 
    machine_startup(args);
}

machine_routines函数,XNU代码路径:/osfmk/arm/machine_routines.c

void
machine_startup(__unused boot_args * args)
{
  machine_conf();
  /*
   * Kick off the kernel bootstrap.
   */
  kernel_bootstrap();
  /* NOTREACHED */
}

kernel_bootstrap函数,XNU代码路径: /osfmk/kern/startup.c

void
kernel_bootstrap(void)
{
  /*
   *  Create a kernel thread to execute the kernel bootstrap.
   */
  kernel_bootstrap_log("kernel_thread_create");
  result = kernel_thread_create((thread_continue_t)kernel_bootstrap_thread, NULL, MAXPRI_KERNEL, &thread);
    /* 省略 */ 
}

kernel_bootstrap_thread函数,XNU代码路径:/osfmk/kern/startup.c,其中vm_pageout方法进行内存报警初始化,bsd_init方法进行Jetsam机制初始化。

static void
kernel_bootstrap_thread(void)
{
    /* 省略 */ 
   //Jetsam机制初始化
  bsd_init();
  //内存报警机制
  vm_pageout();
}

3.3 报警线程创建时机

系统启动时在完成内核初始化工作后,会调用vm_pageout( )方法,创建vm_pageout 守护程序,在vm_pageout函数主要功能是管理页面交换的策略,判断哪些页面需要写回到磁盘,此外的一项功能就是通过kernel_thread_start_priority初始化内存报警线程,刚创建的VM_pressure线程设置为阻塞状态,等待唤醒,XNU代码路径:osfmk/vm/vm_pageout.c。

void
vm_pageout(void)
{
   /* 省略 */
    result = kernel_thread_start_priority((thread_continue_t)vm_pressure_thread, NULL,
      BASEPRI_DEFAULT,
      &thread);

  if (result != KERN_SUCCESS) {
    panic("vm_pressure_thread: create failed");
  }
  thread_deallocate(thread);
     /* 省略 */
 }

3.4 创建内存报警线程

创建内存报警线程,线程名称为VM_pressure,XNU代码路径:osfmk/vm/vm_pageout.c,具体实现如下所示:

#if VM_PRESSURE_EVENTS
void
vm_pressure_thread(void)
{
  static boolean_t thread_initialized = FALSE;

  if (thread_initialized == TRUE) {
    vm_pageout_state.vm_pressure_thread_running = TRUE;
    consider_vm_pressure_events();
    vm_pageout_state.vm_pressure_thread_running = FALSE;
  }

  thread_set_thread_name(current_thread(), "VM_pressure");
  thread_initialized = TRUE;
  assert_wait((event_t) &vm_pressure_thread, THREAD_UNINT);
  thread_block((thread_continue_t)vm_pressure_thread);
}
#endif /* VM_PRESSURE_EVENTS */

3.5 唤醒报警线程

3.5.1 内存发生变化时调用

在手机的内存发生变化的时候就会调用memorystatus_pages_update函数,XNU代码路径:bsd/kern/kern_memorystatus.c, 其中调用核心函数vm_pressure_response,这是内存报警机制的核心模块。

#if VM_PRESSURE_EVENTS
void
vm_pressure_thread(void)
{
  static boolean_t thread_initialized = FALSE;

  if (thread_initialized == TRUE) {
    vm_pageout_state.vm_pressure_thread_running = TRUE;
    consider_vm_pressure_events();
    vm_pageout_state.vm_pressure_thread_running = FALSE;
  }

  thread_set_thread_name(current_thread(), "VM_pressure");
  thread_initialized = TRUE;
  assert_wait((event_t) &vm_pressure_thread, THREAD_UNINT);
  thread_block((thread_continue_t)vm_pressure_thread);
}
#endif /* VM_PRESSURE_EVENTS */

3.5.2 确定新的内存状态值

在vm_pressure_response方法中,通过衡量内存指标来确定是否唤起内存报警线程,进而向各APP发送didReceiveMemoryWarning ,这是内存报警源码的核心模块,XNU代码路径:osfmk/vm/vm_pageout.c。

void  vm_pressure_response(void)
{
      /* 省略 */
    old_level = memorystatus_vm_pressure_level;
  switch (memorystatus_vm_pressure_level) {
  case kVMPressureNormal:
  {
    if (VM_PRESSURE_WARNING_TO_CRITICAL()) {
      new_level = kVMPressureCritical;
    } else if (VM_PRESSURE_NORMAL_TO_WARNING()) {
      new_level = kVMPressureWarning;
    }
    break;
  }

  case kVMPressureWarning:
  case kVMPressureUrgent:
  {
    if (VM_PRESSURE_WARNING_TO_NORMAL()) {
      new_level = kVMPressureNormal;
    } else if (VM_PRESSURE_WARNING_TO_CRITICAL()) {
      new_level = kVMPressureCritical;
    }
    break;
  }

  case kVMPressureCritical:
  {
    if (VM_PRESSURE_WARNING_TO_NORMAL()) {
      new_level = kVMPressureNormal;
    } else if (VM_PRESSURE_CRITICAL_TO_WARNING()) {
      new_level = kVMPressureWarning;
    }
    break;
  }
  default:
    return;
  }
  if (new_level != -1) {
    memorystatus_vm_pressure_level = (vm_pressure_level_t) new_level;
    if ((memorystatus_vm_pressure_level != kVMPressureNormal) || (old_level != memorystatus_vm_pressure_level)) {
      if (vm_pageout_state.vm_pressure_thread_running == FALSE) {
        thread_wakeup(&vm_pressure_thread);
      }
      if (old_level != memorystatus_vm_pressure_level) {
        thread_wakeup(&vm_pageout_state.vm_pressure_changed);
      }
    }
  }
}

memorystatus_vm_pressure_level是全局变量,代表上一次内存状态,接下来根据其不同的值,调用如下方法确定新的内存状态值new_level ,分如下四种情况。

第一、上次处于kVMPressureNormal状态,判断函数VM_PRESSURE_WARNING_TO_CRITICAL()的值,若为true新内存值为kVMPressureCritical,否则判断函数VM_PRESSURE_NORMAL_TO_WARNING()的值,若为true新内存值为kVMPressureWarning,否则为默认值-1;

第二、上次处于kVMPressureWarning、kVMPressureUrgent状态,判断函数VM_PRESSURE_WARNING_TO_NORMAL()的值,若为true,新内存值为kVMPressureNormal,否则判断函数VM_PRESSURE_NORMAL_TO_WARNING()的值,若为true新内存值为kVMPressureCritical,否则为默认值-1;

第三、上次处于kVMPressureCritical状态,判断函数VM_PRESSURE_WARNING_TO_NORMAL()的值,若为true,新内存值为kVMPressureNormal,否则判断函数VM_PRESSURE_CRITICAL_TO_WARNING()的值,若为true新内存值为kVMPressureWarning,否则为默认值-1;

3.5.3 水位等级详情

在XNU代码路径:osfmk/vm/vm_compressor.h,有如下宏定义

#define AVAILABLE_NON_COMPRESSED_MEMORY         (vm_page_active_count + vm_page_inactive_count + vm_page_free_count + vm_page_speculative_count)
#define AVAILABLE_MEMORY                        (AVAILABLE_NON_COMPRESSED_MEMORY + VM_PAGE_COMPRESSOR_COUNT)

#define VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD            (((AVAILABLE_MEMORY) * 10) / (vm_compressor_minorcompact_threshold_divisor ? vm_compressor_minorcompact_threshold_divisor : 10))
#define VM_PAGE_COMPRESSOR_SWAP_THRESHOLD               (((AVAILABLE_MEMORY) * 10) / (vm_compressor_majorcompact_threshold_divisor ? vm_compressor_majorcompact_threshold_divisor : 10))

#define VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD    (((AVAILABLE_MEMORY) * 10) / (vm_compressor_unthrottle_threshold_divisor ? vm_compressor_unthrottle_threshold_divisor : 10))
#define VM_PAGE_COMPRESSOR_SWAP_RETHROTTLE_THRESHOLD    (((AVAILABLE_MEMORY) * 11) / (vm_compressor_unthrottle_threshold_divisor ? vm_compressor_unthrottle_threshold_divisor : 11))

#define VM_PAGE_COMPRESSOR_SWAP_HAS_CAUGHTUP_THRESHOLD  (((AVAILABLE_MEMORY) * 11) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 11))
#define VM_PAGE_COMPRESSOR_SWAP_CATCHUP_THRESHOLD       (((AVAILABLE_MEMORY) * 10) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 10))
#define VM_PAGE_COMPRESSOR_HARD_THROTTLE_THRESHOLD      (((AVAILABLE_MEMORY) * 9) / (vm_compressor_catchup_threshold_divisor ? vm_compressor_catchup_threshold_divisor : 9))

在XNU代码路径:osfmk/vm/vm_compressor.c,有如下赋值,对于iOS系统,走 !XNU_TARGET_OS_OSX分支

#if !XNU_TARGET_OS_OSX
  vm_compressor_minorcompact_threshold_divisor = 20;
  vm_compressor_majorcompact_threshold_divisor = 30;
  vm_compressor_unthrottle_threshold_divisor = 40;
  vm_compressor_catchup_threshold_divisor = 60;
#else /* !XNU_TARGET_OS_OSX */
  /* 省略 */

3.5.3.1 VM_PRESSURE_WARNING_TO_CRITICAL

VM_PRESSURE_WARNING_TO_CRITICAL() 判断内存状态是否从报警到严重,XNU代码路径:osfmk/vm/vm_pageout.c ,在iOS系统中,VM_CONFIG_COMPRESSOR_IS_ACTIVE为YES, 走else逻辑。

boolean_t VM_PRESSURE_WARNING_TO_CRITICAL(void)
{
  if (!VM_CONFIG_COMPRESSOR_IS_ACTIVE) {
    ****
    return FALSE;
  } else {
    return vm_compressor_low_on_space() || (AVAILABLE_NON_COMPRESSED_MEMORY < ((12 * VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD) / 10)) ? 1 : 0;
  }
}

通过前面的宏定义和赋值带入计算表达式,得出如下结论:非压缩可用内存小于总可用内存的12/40。

3.5.3.2 VM_PRESSURE_NORMAL_TO_WARNING

VM_PRESSURE_NORMAL_TO_WARNING()判断内存状态是否从正常到报警,代码路径:osfmk/vm/vm_pageout.c

boolean_t  
VM_PRESSURE_NORMAL_TO_WARNING(void)
{
  /* 省略 */
  return (AVAILABLE_NON_COMPRESSED_MEMORY < VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) ? 1 : 0;
}

同理,带入计算表达式,得出如下结论:非压缩可用内存小于总可用内存的1/2。

3.5.3.3 VM_PRESSURE_WARNING_TO_NORMAL

VM_PRESSURE_WARNING_TO_NORMAL()判断内存状态是否从报警到正常

boolean_t  
VM_PRESSURE_WARNING_TO_NORMAL(void)
{
  /* 省略 */
  return (AVAILABLE_NON_COMPRESSED_MEMORY > ((12 * VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) / 10)) ? 1 : 0;
}

同理,带入计算表达式,得出如下结论: 非压缩可用内存大于总可用内存(压缩+非压缩)的3/5。

3.5.3.4 VM_PRESSURE_CRITICAL_TO_WARNING

VM_PRESSURE_CRITICAL_TO_WARNING()判断内存状态是否从严重到报警

boolean_t 
VM_PRESSURE_CRITICAL_TO_WARNING(void)
{
  /* 省略 */
  return (AVAILABLE_NON_COMPRESSED_MEMORY > ((14 * VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD) / 10)) ? 1 : 0;
}

同理,带入计算表达式,得出如下结论:非压缩可用内存大于总可用内存(压缩+非压缩)的7/20。

3.5.4 判断是否唤起报警线程

如下两个条件满足一个就会唤起vm_pressure_thread。

第一、新的内存状态值不等于kVMPressureNormal。

第二、新的内存状态和老的内存状态不一样。

3.6 报警线程操作

3.6.1 memorystatus_update_vm_pressure实现

从3.3节中我们知道内存报警线程唤醒后执行consider_vm_pressure_events(),XNU代码路径:/bsd/kern/kern_memorystatus_notify.c

void consider_vm_pressure_events(void)
{
  vm_dispatch_memory_pressure();
}
static void vm_dispatch_memory_pressure(void)
{
  memorystatus_update_vm_pressure(FALSE);
}

最终会调用函数memorystatus_update_vm_pressure,XNU代码路径:/bsd/kern/kern_memorystatus_notify.c

kern_return_t
memorystatus_update_vm_pressure(boolean_t target_foreground_process)
{
    /* 省略 */
  if (level_snapshot != kVMPressureNormal) {
    /*
         * 是否处于上一个报警周期
         * next_warning_notification_sent_at_ts代表下一次发送报警通知的最短时间
     */
    level_snapshot = memorystatus_vm_pressure_level;
    if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
      if (next_warning_notification_sent_at_ts) {
                 /
                 * curr_ts表示当前时间,小于下一次发送报警通知的最短时间
                 * 延后执行
                 */
        if (curr_ts < next_warning_notification_sent_at_ts) {
          delay(INTER_NOTIFICATION_DELAY * 4 /* 1 sec */);
          return KERN_SUCCESS;
        }
                //下一次发送报警通知的最短时间设置为零
        next_warning_notification_sent_at_ts = 0;
        memorystatus_klist_reset_all_for_level(kVMPressureWarning);
      }
    } else if (level_snapshot == kVMPressureCritical) {
      /* 省略 */
    }
  }

  while (1) {
    level_snapshot = memorystatus_vm_pressure_level;

    if (prev_level_snapshot > level_snapshot) {
             /*prev_level_snapshot:表示上一一次的等级 
       * 上一次等级小于本次等级,启用滑动窗口逻辑
       */
      if (smoothing_window_started == FALSE) {
        smoothing_window_started = TRUE;
        microuptime(&smoothing_window_start_tstamp);
      }
            /* 省略 */  
    }

    prev_level_snapshot = level_snapshot;
    smoothing_window_started = FALSE;

    memorystatus_klist_lock();
        //从task列表里选取一个task,准备发起内存警告通知
    kn_max = vm_pressure_select_optimal_candidate_to_notify(&memorystatus_klist, level_snapshot, target_foreground_process);
        //没有获取可以发起警告的task
    if (kn_max == NULL) {
      memorystatus_klist_unlock();

      if (level_snapshot != kVMPressureNormal) {
                //延后通知
        if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
          nanoseconds_to_absolutetime(WARNING_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts);

          /* Next warning notification (if nothing changes) won't be sent before...*/
          next_warning_notification_sent_at_ts = mach_absolute_time() + curr_ts;
        }
        if (level_snapshot == kVMPressureCritical) {
          nanoseconds_to_absolutetime(CRITICAL_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts);

          /* Next critical notification (if nothing changes) won't be sent before...*/
          next_critical_notification_sent_at_ts = mach_absolute_time() + curr_ts;
        }
      }
      return KERN_FAILURE;
    }
        //获取选中进程信息
    target_proc = knote_get_kq(kn_max)->kq_p;
    target_pid = target_proc->p_pid;
    task = (struct task *)(target_proc->task);
        //调用is_knote_registered_modify_task_pressure_bits
        //通知选中进程内存报警
    if (level_snapshot != kVMPressureNormal) {
      if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
        if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_WARN, task, 0, kVMPressureWarning) == TRUE) {
          found_candidate = TRUE;
        }
      } else {
        if (level_snapshot == kVMPressureCritical) {
          if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_CRITICAL, task, 0, kVMPressureCritical) == TRUE) {
            found_candidate = TRUE;
          }
        }
      }
    } else {
      if (kn_max->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_NORMAL) {
        task_clear_has_been_notified(task, kVMPressureWarning);
        task_clear_has_been_notified(task, kVMPressureCritical);

        found_candidate = TRUE;
      }
    }

    if (found_candidate == FALSE) {
      proc_rele(target_proc);
      memorystatus_klist_unlock();
      continue;
    }
     /* 省略 */ 
  }

  return KERN_SUCCESS;
}

3.6.2 is_knote_registered_modify_task_pressure_bits通知线程报警

is_knote_registered_modify_task_pressure_bits 通知线程报警,XNU代码路径:/bsd/kern/kern_memorystatus_notify.c

static boolean_t
is_knote_registered_modify_task_pressure_bits(struct knote *kn_max, int knote_pressure_level, task_t task, vm_pressure_level_t pressure_level_to_clear, vm_pressure_level_t pressure_level_to_set)
{
  if (kn_max->kn_sfflags & knote_pressure_level) {
    if (pressure_level_to_clear && task_has_been_notified(task, pressure_level_to_clear) == TRUE) {
      task_clear_has_been_notified(task, pressure_level_to_clear);
    }
    task_mark_has_been_notified(task, pressure_level_to_set);
    return TRUE;
  }

  return FALSE;
}

task_mark_has_been_notified,XNU代码路径:/bsd/kern/task_policy.c

void task_mark_has_been_notified(task_t task, int pressurelevel)
{
  if (task == NULL) {
    return;
  }
  if (pressurelevel == kVMPressureWarning) {
    task->low_mem_notified_warn = 1;
  } else if (pressurelevel == kVMPressureCritical) {
    task->low_mem_notified_critical = 1;
  }
}

四、总结

本文介绍了Mach虚拟内存的特点、内存管理的数据结构以及Mach内核提供的内存操作接口,同时对OC内存分配核心函数alloc做了源码分析,此外对iOS端内存报警机制做了详细的源码分析,关于Jestam机制和libmalloc源码在后续文章做详细介绍,敬请期待。

——END——

参考资料:

[1] objc源码:opensource.apple.com/tarballs/ob…

[2] libsystem_malloc.dylib源码:opensource.apple.com/source/libm…

[3] XNU源码:github.com/apple/darwi…

[4] 《深入解析Mac OS X & iOS操作系统》

[5] Mach内核介绍:

developer.apple.com/library/arc…

[6] Mach系统结构:

developer.apple.com/library/arc…

[7] Mach虚拟内存系统:

developer.apple.com/library/arc…

[8] Mach内存交换空间:

images.apple.com/media/us/os…

推荐阅读

百度APP iOS端内存优化实践-内存管控方案

百度APP iOS端内存优化实践-大块内存监控方案