百度APP iOS端内存优化-原理篇
一、Mach虚拟内存
GEEK TALK
丨1.1 Mach内存简介
丨1.2 Mach虚拟内存的特点
丨1.2.1 虚拟段页式内存管理
丨1.2.2 iOS系统没有交换空间
丨1.2.3 内存压缩技术
丨1.2.4 内存报警
丨1.2.5 Jetsam机制
丨1.3 Mach内存管理数据结构
丨1.4 Mach内核提供的内存操作接口
二、内存分配函数alloc源码分析
GEEK TALK
丨2.1 objc_rootAlloc函数
+ (id)alloc {return _objc_rootAlloc(self);}id _objc_rootAlloc(Class cls){return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);}
丨2.2 callAlloc函数
static ALWAYS_INLINE idcallAlloc(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/* 省略 */}
丨2.3 objc_rootAllocWithZone函数
NEVER_INLINE id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused){// allocWithZone under __OBJC2__ ignores the zone parameterreturn _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);}/* 省略 */}
丨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;}
uint32_t alignedInstanceSize() {return word_align(unalignedInstanceSize());}uint32_t unalignedInstanceSize() const {ASSERT(isRealized());return data()->ro()->instanceSize;}
# define WORD_MASK 7ULstatic inline uint32_t word_align(uint32_t x) {return (x + WORD_MASK) & ~WORD_MASK;}
丨2.6 malloc_zone_calloc函数
void *calloc(size_t num_items, size_t size){return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);}MALLOC_NOINLINEstatic 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);}
三、内存报警源码分析
GEEK TALK
丨3.1 总体流程图
丨3.2 系统启动初始化
void arm_init( boot_args *args){ /* 省略 */machine_startup(args);}
voidmachine_startup(__unused boot_args * args){machine_conf();/** Kick off the kernel bootstrap.*/kernel_bootstrap();/* NOTREACHED */}
voidkernel_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);/* 省略 */}
static voidkernel_bootstrap_thread(void){/* 省略 *///Jetsam机制初始化bsd_init();//内存报警机制vm_pageout();}
丨3.3 报警线程创建时机
voidvm_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 创建内存报警线程
#if VM_PRESSURE_EVENTSvoidvm_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 内存发生变化时调用
#if VM_PRESSURE_EVENTSvoidvm_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 确定新的内存状态值
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);}}}}
丨3.5.3 水位等级详情
#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))
#if !XNU_TARGET_OS_OSXvm_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
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;}}
丨3.5.3.2 VM_PRESSURE_NORMAL_TO_WARNING
boolean_tVM_PRESSURE_NORMAL_TO_WARNING(void){/* 省略 */return (AVAILABLE_NON_COMPRESSED_MEMORY < VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) ? 1 : 0;}
丨3.5.3.3 VM_PRESSURE_WARNING_TO_NORMAL
boolean_tVM_PRESSURE_WARNING_TO_NORMAL(void){/* 省略 */return (AVAILABLE_NON_COMPRESSED_MEMORY > ((12 * VM_PAGE_COMPRESSOR_COMPACT_THRESHOLD) / 10)) ? 1 : 0;}
丨3.5.3.4 VM_PRESSURE_CRITICAL_TO_WARNING
boolean_tVM_PRESSURE_CRITICAL_TO_WARNING(void){/* 省略 */return (AVAILABLE_NON_COMPRESSED_MEMORY > ((14 * VM_PAGE_COMPRESSOR_SWAP_UNTHROTTLE_THRESHOLD) / 10)) ? 1 : 0;}
丨3.5.4 判断是否唤起报警线程
丨3.6 报警线程操作
丨3.6.1 memorystatus_update_vm_pressure实现
void consider_vm_pressure_events(void){vm_dispatch_memory_pressure();}static void vm_dispatch_memory_pressure(void){memorystatus_update_vm_pressure(FALSE);}
kern_return_tmemorystatus_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);//没有获取可以发起警告的taskif (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通知线程报警
static boolean_tis_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;}
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;}}
四、总结
GEEK TALK
END