OOM是什么?
OOM的全稱是out of memory,字面意思也就是指內存超出了限制。在iOS中的OOM是由操作系統的Jetsam機制出發的crash的一種。由OOM導致的crash無法通過監控singal獲取異常信息,所以對于OOM的監控只能間接實現。
OOM和棧溢出有關系嗎?
程序在運行過程中,為了臨時存取數據的需要,一般都要分配一些內存空間,通常稱這些空間為緩沖區。如果向緩沖區中寫入超過其本身長度的數據,以致于緩沖區無法容納,就會造成緩沖區以外的存儲單元被改寫,這種現象就稱為緩沖區溢出 緩沖區長度一般與用戶自己定義的緩沖變量的類型有關。棧溢出就是緩沖區溢出的一種。
一般的奔潰日志上會有"Stack Guard"字樣,一般會提示EXC_BAD_ACCESS
OOM 產生的原因
說說Jetsam機制
Jetsam時iOS系統的單獨的進程,對于內存管理則是BSD層創建的優先級最高的常駐線程VM_memorystatus,可以管理系統的內存占用,當發現內存緊張時候會根據優先級殺掉其他應用程序進程。可以簡單理解為內存管理的的奔潰處理機制就是Jetsam機制。
macOS或者windows系統,當應用程序緊張的時候,可以通過SWAP內存交換機制實現把物理內存中的一部分內容交換到磁盤上去,利用磁盤空間擴展內存空間。對于移動設備來說一般沒有內存交換機制,原因在于移動設備的存儲介質也就是閃存,而閃存的性能和使用壽命是無法和電腦硬盤相比的,所以當內存緊張時,就會系統的Jetsam就會殺死應用程序。
Compressed memory
iOS 上沒有Disk swap機制,取而代之使用 Compressed memory。從 OS X Mavericks Core Technology Overview 文檔中可以了解到該技術在內存緊張時能夠將最近使用過的內存占用壓縮至原有大小的一半以下,并且能夠在需要時解壓復用。它在節省內存的同時提高了系統的響應速度,其特點可以歸結為:
Shrinks memory usage 減少了不活躍內存占用
Improves power efficiency 改善電源效率,通過壓縮減少磁盤IO帶來的損耗
Minimizes CPU usage 壓縮/解壓十分迅速,能夠盡可能減少 CPU 的時間開銷
Is multicore aware 支持多核操作
本質上,Compressed memory 也是 Dirty memory
因此, memory footprint = dirty size + compressed size ,這也就是我們需要并且能夠嘗試去減少的內存占用。
NSCache 分配的內存實際上是 Purgeable Memory,可以由系統自動釋放。NSCache 與 NSPureableData 的結合使用既能讓系統根據情況回收內存,也可以在內存清理的同時移除相關對象。
Jetsam機制殺死進程的順序
Jetsam機制殺死進程的順序一般基于應用程序的優先級確定的,優先級低的進程先于優先級高的進程被殺死。在iOS系統中應用程序的優先級時不可能高于操作系統和內核的。前臺的應用程序的優先級高于后臺應用程序,對于多個后臺程序的優先級也是不完全一樣的,系統會對每一個進程的優先級進行動態調整。例如如果耗費CPU太多就降低優先級,如果一個線程過度挨餓CPU則會提升其優先級。
需要注意的是,JETSAM不一定只殺一個進程,他可能會大殺特殺,殺掉N多進程。
typedef struct memstat_bucket {
TAILQ_HEAD(, proc) list;//一個TAILQ_HEAD的雙向鏈表,用來存放這個優先級下面的進程
int count;//進程的個數,也就是上面list的數量
} memstat_bucket_t;
//內核里面對于所有的進程都有一個優先級的分布,通過一個數組維護,數組每一項是一個進程的list。這個數組的大小是JETSAM_PRIORITY_MAX + 1
因為apple的內核xnu代碼是開源的,我們可以從kern_memorystatus.h中獲取到相關的
進程優先級的聲明,數值越大表明優先級越高,前臺進程JETSAM_PRIORITY_FOREGROUND(10)大于后臺進程JETSAM_PRIORITY_BACKGROUND(3)
#ifndef SYS_MEMORYSTATUS_H
#define SYS_MEMORYSTATUS_H
#include <stdint.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/param.h>
#include <mach_debug/zone_info.h>
#define MEMORYSTATUS_ENTITLEMENT "com.apple.private.memorystatus"
#define JETSAM_PRIORITY_REVISION 2
#define JETSAM_PRIORITY_IDLE_HEAD -2
/* The value -1 is an alias to JETSAM_PRIORITY_DEFAULT */
#define JETSAM_PRIORITY_IDLE 0
#define JETSAM_PRIORITY_IDLE_DEFERRED 1 /* Keeping this around till all xnu_quick_tests can be moved away from it.*/
#define JETSAM_PRIORITY_AGING_BAND1 JETSAM_PRIORITY_IDLE_DEFERRED
#define JETSAM_PRIORITY_BACKGROUND_OPPORTUNISTIC 2
#define JETSAM_PRIORITY_AGING_BAND2 JETSAM_PRIORITY_BACKGROUND_OPPORTUNISTIC
#define JETSAM_PRIORITY_BACKGROUND 3
#define JETSAM_PRIORITY_ELEVATED_INACTIVE JETSAM_PRIORITY_BACKGROUND
#define JETSAM_PRIORITY_MAIL 4
#define JETSAM_PRIORITY_PHONE 5
#define JETSAM_PRIORITY_UI_SUPPORT 8
#define JETSAM_PRIORITY_FOREGROUND_SUPPORT 9
#define JETSAM_PRIORITY_FOREGROUND 10
#define JETSAM_PRIORITY_AUDIO_AND_ACCESSORY 12
#define JETSAM_PRIORITY_CONDUCTOR 13
#define JETSAM_PRIORITY_HOME 16
#define JETSAM_PRIORITY_EXECUTIVE 17
#define JETSAM_PRIORITY_IMPORTANT 18
#define JETSAM_PRIORITY_CRITICAL 19
#define JETSAM_PRIORITY_MAX 21
/* TODO - tune. This should probably be lower priority */
#define JETSAM_PRIORITY_DEFAULT 18
#define JETSAM_PRIORITY_TELEPHONY 19
/* Compatibility */
#define DEFAULT_JETSAM_PRIORITY 18
#define DEFERRED_IDLE_EXIT_TIME_SECS 10
#define KEV_MEMORYSTATUS_SUBCLASS 3
如何捕獲OOM
關于didReceiveMemoryWarning方法
可以在UIViewController中實現didReceiveMemoryWarning方法,也可以在AppDelegate中實現applicationDidReceiveMemoryWarning:方法,也可以在注冊UIApplicationDidReceiveMemoryWarningNotification通知處理。
出現OOM前一定會調用didReceiveMemoryWarning么?
答案當然是不一定的。因為didReceiveMemoryWarning調用實在主線程的,如果瞬間申請了大塊內存,而此時主線程正忙于其他的事情,此時會導致發生了OOM而無法獲取didReceiveMemoryWarning調用。
觸發didReceiveMemoryWarning之后一定會導致OOM嗎?
顯示也是不會的,因為當收到內存警告,如果之后內存下降了,也不會導致OOM
內存閥值的獲取
可以在即將到達內存閥值時,處理對象釋放嗎?
理論上可以這么處理的,例如我們知道了當前設備/系統的內存閥值,我們定義一個范圍,例如監控到當前內存為內存閥值的80%,此處通過開源框架KSCrash或者BSBacktraceLogger獲取,當然也可以自己實現,可以參考文章《iOS 如何抓取線程的“方法調用棧”?》。對于內存分析,僅僅實現對于堆棧的回溯是不夠的,我們更關心的是找出內存最大的對象,進行引用關系的分析,在Debug下可以參考FLEX庫的實現,通過malloc_get_all_zones可以獲取所有堆區的對象,通過objc_getClass獲取對應的對象名,通過class_getInstanceSize獲取單個對象的大小。
kern_return_t result = malloc_get_all_zones(mach_task_self(), &memory_reader, &zones, &zoneCount);
if (result == KERN_SUCCESS) {
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
3.
if (zone->introspect && zone->introspect->enumerator) {
zone->introspect->enumerator(mach_task_self(), (__bridge void *)(block), MALLOC_PTR_IN_USE_RANGE_TYPE, zones[i], &memory_reader, &range_callback);
}
}
}
首先我們要了解內存閥值的獲取
可以開辟子線程,循環申請1M的內存直到出現收到內存警告和獲取OOM的值為止。至于為什么是申請IM的內存,在apple的A7處理器之前,物理內存和虛擬內存都是按照4KB進行分頁的,但是A7之后虛擬內存是按照16KB分頁的,物理內存還是4KB分頁,如果申請的內存顆粒度小于虛擬內存頁的大小意義不大,另外考慮到統計的顆粒度,也可以是其他的數值。
//獲取內存當前占用,不需要使用taskInfo. resident_size獲取,因為resident_size不準確
- (int)limitSizeOfMemory {
if (@available(iOS 13.0, *)) {
return os_proc_available_memory() / 1024.0 / 1024.0;;
} else {
task_vm_info_data_t taskInfo;
mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
kern_return_t kernReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&taskInfo, &infoCount);
if (kernReturn != KERN_SUCCESS) {
return 0;
}
return (int) taskInfo.phys_footprint / 1024.0 / 1024.0;
}
return 0;
}
在stackoverflow上有人對不同設備的OOM閥值進行了統計
如何監控內存分配
FBAllocationTracker
iOS中最早可以追溯到Facebook的FBAllocationTracker庫。實現原理為hook NSObject的alloc/dealloc方法,保存實例對象。對于部分類(NSCFTimer、NSAutoreleasePool、NSTaggedPointerStringCStringContainer)由于性能和crash原因,忽略hook。在下一次啟動時通過排除發分析上一次的結束進程的原因是否為OOM導致的。從如下幾個緯度采取排除法進行分析,如果終止進程不是下面行為導致的,則判定為FOOM(前臺應用OOM),因為后臺應用的OOM可能是前臺應用占用內存過大被動導致的系統強殺。
- app版本號是否發生了改變
- app是否發生了crash
- 是否為用戶手動退出
- 操作系統版本升級
- 之前進程終止是否在后臺
此方案因為涉及NSObject的alloc/delloc進行全量的hock,所以對于性能會有一定的影響。且不一定繼承自NSObject的對象都會走alloc方法,例如:NSData創建對象的類靜態方法沒有調用+[NSObject alloc],里面實現是調用C方法NSAllocateObject來創建對象,也就是說這類方式創建的OC對象無法通過hook來獲取OC類名。因為Fundation框架沒有開源,但Core Foundation框架的源代碼,以及通過調用NSObject類進行內存管理部分的源代碼是公開的。但是Fundation部分可以通過GNU step的libobjc2獲取
+ (id)alloc {
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return NSAllocateObject(self, 0, z);
}
OOMDetector
2018年QQ開源了他們自己的監控組件OOMDetector。主要參考了系統的libmalloc庫中stack_logging_disk.c文件,可以通過malloc_logger實現對于malloc/free的監控,而__syscall_logger可以實現對于vm_allocate, vm_deallocate, mmap, munmap的監控。但是__syscall_logger是私有方法,所以在OOMDetector組件中使用中也提到了建議只是在Debug環境下使用。
// We set malloc_logger to NULL to disable logging, if we encounter errors
// during file writing
typedef void (malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip);
extern malloc_logger_t *malloc_logger;
extern malloc_logger_t *__syscall_logger; // use this to set up syscall logging (e.g., vm_allocate, vm_deallocate, mmap, munmap)
OOMDetector組件使用的聲明
__syscall_logger函數
#define USE_VM_LOGGER
#ifdef USE_VM_LOGGER //監控非malloc方式直接申請內存
typedef void (malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip);
extern malloc_logger_t* __syscall_logger;
#endif
malloc_logger函數
#import <Foundation/Foundation.h>
#import <malloc/malloc.h>
#import "CStackHelper.h"
#ifdef __cplusplus
extern "C" {
#endif
extern malloc_zone_t *global_memory_zone;
typedef void (malloc_logger_t)(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t num_hot_frames_to_skip);
extern malloc_logger_t* malloc_logger;
void common_stack_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t result, uint32_t backtrace_to_skip);
#ifdef __cplusplus
}
#endif
matrix
微信在2019年開源了內存和幀率CPU耗電等監控方案matrix,其中有涉及到OOM情況的監控。修改malloc_default_zone函數返回的malloc_zone_t結構體里的malloc、free等函數指針,也是可以監控堆內存分配,效果等同于malloc_logger;
apple源碼中的_malloc_zone_t結構如下
typedef struct _malloc_zone_t {
void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */
void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */
size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size);//malloc函數調用
void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr);//free 函數調用
void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size);
void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone);
const char *zone_name;
unsigned (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);
struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);
unsigned version;
void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size);
void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size);
size_t (* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal);
boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr);
} malloc_zone_t;
而在matrix中對于malloc_zone_t修改部分如下
static malloc_zone_t *inter_zone = malloc_create_zone(getpagesize(), 0);
#pragma mark Allocation/Deallocation Function without Logging
void *inter_malloc(uint64_t memSize)
{
__set_thread_to_ignore_logging(current_thread_id(), true);
void *allocatedMem = inter_zone->malloc(inter_zone, (size_t)memSize);
__set_thread_to_ignore_logging(current_thread_id(), false);
return allocatedMem;
}
void *inter_realloc(void *oldMem, size_t newSize)
{
__set_thread_to_ignore_logging(current_thread_id(), true);
void *newMem = inter_zone->realloc(inter_zone, oldMem, newSize);
__set_thread_to_ignore_logging(current_thread_id(), false);
return newMem;
}
void inter_free(void *ptr)
{
__set_thread_to_ignore_logging(current_thread_id(), true);
inter_zone->free(inter_zone, ptr);
__set_thread_to_ignore_logging(current_thread_id(), false);
}
matrix中對于dispatch內存監控使用的是fishhock庫的rebind_symbols函數,具體的代碼邏輯如下
MatrixAsyncHook.m類
//hock處理的宏
#define BEGIN_HOOK(func) \
ks_rebind_symbols((struct ks_rebinding[2]){{#func, WRAP(func), (void *)&ORIFUNC(func)}}, 1);
//開始處理dispatch相關的hock
- (void)beginHook
{
// 1. hook dispatch
BEGIN_HOOK(dispatch_async);
BEGIN_HOOK(dispatch_after);
BEGIN_HOOK(dispatch_barrier_async);
BEGIN_HOOK(dispatch_async_f);
BEGIN_HOOK(dispatch_after_f);
BEGIN_HOOK(dispatch_barrier_async_f);
}
為了更好的監控OOM,Matrix在Debug環境下也使用了私有API,通過下面宏的定義可以看出
#ifdef DEBUG
#define USE_PRIVATE_API
#endif
而正式開始監控OOM的方法為enable_memory_logging
int enable_memory_logging(const char *log_dir)
{
err_code = MS_ERRC_SUCCESS;
//允許使用私有API時候,通過"stack_logging_enable_logging"判斷當前環境是否有debug工具在使用
#ifdef USE_PRIVATE_API
// stack_logging_enable_logging
int *stack_logging_enable_logging = (int *)dlsym(RTLD_DEFAULT, "stack_logging_enable_logging");
if (stack_logging_enable_logging != NULL && *stack_logging_enable_logging != 0) {
is_debug_tools_running = true;
}
#endif
// Check whether there's any analysis tool process logging memory.
if (is_debug_tools_running || is_being_debugged()) {
return MS_ERRC_ANALYSIS_TOOL_RUNNING;
}
logging_is_enable = true;
//初始化二叉樹存儲保存對象的alloc事件
allocation_event_writer = open_or_create_allocation_event_db(log_dir);
if (allocation_event_writer == NULL) {
return err_code;
}
//初始化二叉樹保存棧幀
stack_frames_writer = open_or_create_stack_frames_db(log_dir);
if (stack_frames_writer == NULL) {
return err_code;
}
//event_buffer = open_or_create_allocation_event_buffer(log_dir);
event_buffer = open_or_create_allocation_event_buffer_static();
if (event_buffer == NULL) {
return err_code;
}
//初始化處理的pthread
if (__prepare_working_thread() == false) {
__malloc_printf("create writing thread fail");
return MS_ERRC_WORKING_THREAD_CREATE_FAIL;
}
//通過dyld判斷是否可以獲取images信息
if (!prepare_dyld_image_logger(log_dir)) {
return err_code;
}
//準備處理hock alloc
if (!prepare_object_event_logger(log_dir)) {
return err_code;
}
//監控系統的malloc_logger方法
malloc_logger = __memory_event_callback;
#ifdef USE_PRIVATE_API
// 如果在debug環境,則監控私有方法__syscall_logger
syscall_logger = (malloc_logger_t **)dlsym(RTLD_DEFAULT, "__syscall_logger");
if (syscall_logger != NULL) {
*syscall_logger = __memory_event_callback;
}
#endif
//至此,表明可以正常監控OOM
return MS_ERRC_SUCCESS;
}
在其中的prepare_object_event_logger方法簡要分析如下
bool prepare_object_event_logger(const char *log_dir)
{
//加鎖
object_types_mutex = __malloc_lock_init();
object_types_file = __init_object_type_file(log_dir);
if (object_types_file == NULL) {
return false;
}
// Insert vm memory type names,需要處理的vm memory類型,這個在matrix內部以一個數組維護
for (int i = 0; i < sizeof(vm_memory_type_names) / sizeof(char *); ++i) {
uintptr_t str_hash = __string_hash(vm_memory_type_names[i]);
uint32_t type = object_types_file->object_type_list->size() + 1;
object_types_file->object_type_exists->insert(str_hash);
object_types_file->object_type_list->insert(object_type(type, vm_memory_type_names[i]));
}
#ifdef USE_PRIVATE_API
//如果是在Debug環境,也就是說可以使用私有API,則通過__CFObjectAllocSetLastAllocEventNameFunction和__CFOASafe實現對于NSData等對象的hock處理。
// __CFObjectAllocSetLastAllocEventNameFunction
object_set_last_allocation_event_name_funcion = (void (**)(void *, const char *))dlsym(RTLD_DEFAULT, "__CFObjectAllocSetLastAllocEventNameFunction");
if (object_set_last_allocation_event_name_funcion != NULL) {
*object_set_last_allocation_event_name_funcion = object_set_last_allocation_event_name;
}
// __CFOASafe
object_record_allocation_event_enable = (bool *)dlsym(RTLD_DEFAULT, "__CFOASafe");
if (object_record_allocation_event_enable != NULL) {
*object_record_allocation_event_enable = true;
}
#endif
//對于NSObjct的alloc方法的hock
nsobject_hook_alloc_method();
return true;
}
對于非allocation/deallocation管控的內存,采用的是alloc和Debug下的私有方法__CFOASafe和__CFObjectAllocSetLastAllocEventNameFunction處理。
malloc_logger
對于前文提到的OOMDetector和Matrix開源庫中出現的對于malloc_logger回調函數的監控。malloc_logger是如何監控到應用層面的內存分配的呢?
在 libmalloc庫中的以下關于內存相關的方法 malloc_zone_malloc, malloc_zone_calloc, malloc_zone_valloc, malloc_zone_realloc, malloc_zone_free, malloc_zone_free_definite_size, malloc_zone_memalign等函數內部都會調用malloc_logger,所以我們只需要監控malloc_logger就可以實現對于內存的alloc與free的監控。
對于內存監控數據的存儲
sqlite
在SQLite中,每一個表(含多字段)都用一個唯一的B-tree存儲,數據庫有多個表就有多個B-tree。而B-樹的性能總是等價于二分查找(與M值無關)。
有人對移動端常用數據庫性能做了比較,SQLite 3在對于1萬條簡單數據查詢基本在331ms,而WCDB為690ms。而一個app啟動時的對象創建和銷毀數量是巨大的,以微信為例,在啟動10秒內,已經創建了80萬對象,釋放了50萬。保守估計,按照sqlite3計算操作數據庫的時間基本在3s左右。
數據庫種類 | SQLite3 | realm | WCDB |
---|---|---|---|
簡單查詢一萬次耗時 | 331ms | 699ms | 690ms |
9萬條數據基礎上連續單條插入一萬條數據耗時 | 1462ms | 32851ms | 750ms |
dispatch 100個block來查詢一萬次耗時 | 150ms | 205ms | 199ms |
二叉樹
二叉查找樹又稱二叉搜索樹,二叉排序樹,特點如下:
- 左子樹上所有結點值均小于根結點
- 右子樹上所有結點值均大于根結點
- 結點的左右子樹本身又是一顆二叉查找樹
- 二叉查找樹中序遍歷得到結果是遞增排序的結點序列。
查找最好時間復雜度O(logN),最壞時間復雜度O(N)。和B-Tree時間復雜度是一樣的。
平衡二叉樹
微信和QQ采用的都是二叉樹存儲。對于為什么不采用數據庫,因為在app啟動后,需要創建大量對象以及釋放大量對象,采用二叉樹結構比較合適,一般情況下二叉樹的時間復雜度為log(N),但是傳統二叉樹在操作后可能變為單鏈表情形,此時的時間復雜度就為O(N)了。
Matrix中是用數組實現二叉樹。具體做法是父結點的左右孩子由以往的指針類型改成整數類型,代表孩子在數組的下標;刪除結點時,被刪除的結點存放上一個被釋放的結點所在數組下標。
數據上報
對于數據上報,采取的是對上次結束進程的判斷,基本邏輯和Facebook的開源框架FBAllocationTracker基本類似,具體的流程如下
實際開發代碼層面如何避免
避免內存泄露
內存泄露會導致對象無法釋放,從而長駐內存,如果內存泄露的對象數量一旦很多極易引發OOM。常見的內存泄露包括的block的操作,以及Fundation框架操作的時候及時free對象,UIGraphicsBeginImageContext和UIGraphicsEndImageContext的成對出現等。在Debug環境可以使用Memeory Graph進行分析分寸泄露,當然也可以解除第三方工具,如MemoryLeaks處理等。
緩存盡量使用NSCache
因為NSCahe內部apple幫我們實現了在內存達到閥值時(非OOM觸發閥值),內部會根據LRC算法主動處理對象的釋放。對于緩存部分盡量使用NSCache。
合理使用自動釋放池
通常autoreleased的對象在runloop結束時才釋放。如果在一些大型循環中,此時內存會瞬間增長,而自動釋放池可以更及時的釋放對象。
對于圖片的操作
對于iamge縮放的操作,可以用如下代碼實現
//常見的UIimage縮放寫法:
- (UIImage *)scaleImage:(UIImage *)image newSize:(CGSize)newSize{
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
//節約內存的ImageIO縮放寫法:
+ (UIImage *)scaledImageWithData:(NSData *)data withSize:(CGSize)size scale:(CGFloat)scale orientation:(UIImageOrientation)orientation{
CGFloat maxPixelSize = MAX(size.width, size.height);
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSDictionary *options = @{(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : (__bridge id)kCFBooleanTrue,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : [NSNumber numberWithFloat:maxPixelSize]};
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
UIImage *resultImage = [UIImage imageWithCGImage:imageRef scale:scale orientation:orientation];
CGImageRelease(imageRef);
CFRelease(sourceRef);
return resultImage;
}
對于圖片緩存的操作,可以在頁面pop或dimiss的時候,及時清空圖片的緩存。另外,對于下發或者使用的圖片,盡量避免圖片的縮放。如果是大圖片也可以減少圖片壓縮的空間。
參考文獻:
https://juejin.cn/post/6844903902169710600
http://satanwoo.github.io/2017/10/18/abort/
https://blog.csdn.net/TuGeLe/article/details/104004692
http://www.lxweimin.com/p/7a8fafa1ba34
https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/kern_memorystatus.h.auto.html
https://zhuanlan.zhihu.com/p/138755187
https://stackoverflow.com/questions/5887248/ios-app-maximum-memory-budget/15200855#15200855
https://wetest.qq.com/lab/view/367.html
https://github.com/Tencent/matrix
http://www.cocoachina.com/articles/485753
http://www.lxweimin.com/p/8187eddbe422
https://www.dazhuanlan.com/xayljq/topics/1667837
http://www.lxweimin.com/p/b3b2aa3722a4
https://blog.csdn.net/cdy15626036029/article/details/81014959