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

一、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。

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

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) – 从体系范围的角度供给有关虚拟内存的核算信息。

百度APP iOS端内存优化-原理篇
在 iOS ARM64机型中page size是16K,在 JetsamEvent 开端的体系日志里pageSize 代表当时设备物理内存页的巨细。
百度APP iOS端内存优化-原理篇

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_allocate allocates “zero fill” memory in the specfied map
mach_vm_deallocate deallocates the specified range of addresses in the specified address map
mach_vm_protect sets the protection of the specified range in the specified map
mach_vm_map maps a memory object to a task’s address space
mach_vm_page_query query 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 总体流程图

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

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端内存优化实践-大块内存监控计划