iOS-底层原理36:内存优化(一) 野指针探测

简介: iOS-底层原理36:内存优化(一) 野指针探测

本文主要讲解两种野指针检测的原理及实现


技术点:野指针探测


本文的主要目的是理解野指针的形成过程以及如何去检测野指针


引子


在介绍野指针之前,首先说下目前的异常处理类型,附上苹果官网链接


异常类型


异常大致可以分为两类:


  • 1、软件异常:主要是来自kill()、pthread_kill()、iOS中的NSException未捕获、absort等
  • 2、硬件异常:硬件的信号始于处理器trap,是和平台相关的,野指针崩溃大部分是硬件异常


而在处理异常时,需要关注两个概念


  • Mach异常Mach层捕获
  • UNIX信号BSD层获取


iOS中的POSIX API就是通过Mach之上的BSD层实现的,如下图所示

image.png

  • Mach 是一个受 Accent 启发而搞出的Unix兼容系统。
  • BSD层是建立在Mach之上,是XNU中一个不可分割的一部分。BSD负责提供可靠的、现代的API
  • POSIX表示可移植操作系统接口(Portable Operating System Interface)


所以,综上所述,Mach异常和UNIX信号存在对应的关系

image.png

  • 1、硬件异常流程:硬件异常 -> Mach异常 -> UNIX信号
  • 2、软件异常流程:软件异常 -> UNIX信号


Mach异常与UNIX信号的转换


下面是Mach异常UNIX信号 的转换关系代码,来自 xnu 中的 bsd/uxkern/ux_exception.c

switch(exception) {
case EXC_BAD_ACCESS:
    if (code == KERN_INVALID_ADDRESS)
        *ux_signal = SIGSEGV;
    else
        *ux_signal = SIGBUS;
    break;
case EXC_BAD_INSTRUCTION:
    *ux_signal = SIGILL;
    break;
case EXC_ARITHMETIC:
    *ux_signal = SIGFPE;
    break;
case EXC_EMULATION:
    *ux_signal = SIGEMT;
    break;
case EXC_SOFTWARE:
    switch (code) {
    case EXC_UNIX_BAD_SYSCALL:
    *ux_signal = SIGSYS;
    break;
    case EXC_UNIX_BAD_PIPE:
    *ux_signal = SIGPIPE;
    break;
    case EXC_UNIX_ABORT:
    *ux_signal = SIGABRT;
    break;
    case EXC_SOFT_SIGNAL:
    *ux_signal = SIGKILL;
    break;
    }
    break;
case EXC_BREAKPOINT:
    *ux_signal = SIGTRAP;
    break;
}

将其对应关系汇总成一个表格,如下所示

image.png

  • 其中Mach异常有以下

image.png

  • UNIX信号有以下几种

image.png

image.png


野指针


所指向的对象被释放或者收回,但是该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址。这个指针就是野指针


野指针分类


这个参考腾讯Bugly团队的总结,大致分为两类


  • 内存没被覆盖
  • 内存被覆盖


如下图所示

image.png

为什么OC野指针的crash这么多?


我们一般在app发版前,都会经过多轮的自测、内侧、灰度测试等,按照常理来说,大部分的crash应该都被覆盖了,但是由于野指针的随机性,使得经常在测试时不会出现crash,而是在线上出现crash,这对app体验来说是非常致命的


而野指针的随机性问题大致可以分为两类:


  • 1、跑不进出错的逻辑,执行不到出错的代码,这种可以通过提高测试场景覆盖率来解决
  • 2、跑进有问题的逻辑,但是野指针指向的地址并不一定会导致crash,原因是因为:野指针其本质是一个指向已经删除的对象受限内存区域指针。这里说的OC野指针,是指OC对象释放后指针未置空而导致的野指针。这里不必现的原因是因为dealloc执行后只是告诉系统,这片内存我不用了,而系统并没有让这片内存不能访问


野指针解决思路


这里主要是借鉴Xcode中的两种处理方案:


image.png

1、Malloc Scribble ,其官方解释如下:申请内存 alloc 时在内存上填0xAA,释放内存 dealloc 在内存上填 0x55

image.png

2、Zombie Objects,其官方解释如下:一个对象已经解除了它的引用,已经被释放掉,但是此时仍然是可以接受消息,这个对象就叫做Zombie Objects(僵尸对象)。这种方案的重点就是将释放的对象,全都转为僵尸对象


image.png

两种方案对比


  • 1、僵尸对象 相比 Malloc Scribble不需要考虑会不会崩溃的问题,只要野指针指向僵尸对象,那么再次访问野指针就一定会崩溃
  • 2、僵尸对象这种方式,不如Malloc Scribble覆盖面广,可以通过hook free方法将c函数也包含在其中


1、Malloc Scribble


思路:当访问到对象内存中填充的是0xAA、0x55时,程序就会出现异常


  • 申请内存 alloc 时在内存上填0xAA
  • 释放内存 dealloc 在内存上填 0x55


以上的申请和释放的填充分别对应一下两种情况


  • 申请:没有做初始化就直接被访问
  • 释放:释放后访问


所以综上所述,针对野指针,我们的解决办法是:在对象释放时做数据填充0x55即可。关于对象的释放流程可以参考这篇文章iOS-底层原理 33:内存管理(一)TaggedPointer/retain/release/dealloc/retainCount 底层分析


野指针探测实现1


这个实现主要依据腾讯Bugly工程师:陈其锋的分享,在其代码中的主要思路是


  • 1、通过fishhook替换C函数free方法为自定义的safe_free,类似于Method Swizzling
  • 2、在safe_free方法中对已经释放变量的内存,填充0x55,使已经释放变量不能访问,从而使某些野指针的crash从不必现安变成必现
  • 为了防止填充0x55的内存被新的数据内容填充,使野指针crash变成不必现,在这里采用的策略是,safe_free不释放这片内存,而是自己保留着,即safe_free方法中不会真的调用free。
  • 同时为了防止系统内存过快消耗(因为要保留内存),需要在保留的内存大于一定值时释放一部分,防止被系统杀死,同时,在收到系统内存警告时,也需要释放一部分内存
  • 3、发生crash时,得到的崩溃信息有限,不利于问题排查,所以这里采用代理类(即继承自NSProxy的子类),重写消息转发的三个方法(参考这篇文章iOS-底层原理 14:消息流程分析之 动态方法决议 & 消息转发),以及NSObject的实例方法,来获取异常信息。但是这的话,还有一个问题,就是NSProxy只能做OC对象的代理,所以需要在safe_free中增加对象类型的判断


以下是完整的野指针探测实现代码

  • 引入fishhook

image.png

  • 实现NSProxy的代理子类
<!--1、MIZombieProxy.h-->
@interface MIZombieProxy : NSProxy
@property (nonatomic, assign) Class originClass;
@end
<!--2、MIZombieProxy.m-->
#import "MIZombieProxy.h"
@implementation MIZombieProxy
- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.originClass instancesRespondToSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.originClass instanceMethodSignatureForSelector:sel];
}
- (void)forwardInvocation: (NSInvocation *)invocation
{
    [self _throwMessageSentExceptionWithSelector: invocation.selector];
}
#define MIZombieThrowMesssageSentException() [self _throwMessageSentExceptionWithSelector: _cmd]
- (Class)class{
    MIZombieThrowMesssageSentException();
    return nil;
}
- (BOOL)isEqual:(id)object{
    MIZombieThrowMesssageSentException();
    return NO;
}
- (NSUInteger)hash{
    MIZombieThrowMesssageSentException();
    return 0;
}
- (id)self{
    MIZombieThrowMesssageSentException();
    return nil;
}
- (BOOL)isKindOfClass:(Class)aClass{
    MIZombieThrowMesssageSentException();
    return NO;
}
- (BOOL)isMemberOfClass:(Class)aClass{
    MIZombieThrowMesssageSentException();
    return NO;
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol{
    MIZombieThrowMesssageSentException();
    return NO;
}
- (BOOL)isProxy{
    MIZombieThrowMesssageSentException();
    return NO;
}
- (NSString *)description{
    MIZombieThrowMesssageSentException();
    return nil;
}
#pragma mark - MRC
- (instancetype)retain{
    MIZombieThrowMesssageSentException();
    return  nil;
}
- (oneway void)release{
    MIZombieThrowMesssageSentException();
}
- (void)dealloc
{
    MIZombieThrowMesssageSentException();
    [super dealloc];
}
- (NSUInteger)retainCount{
    MIZombieThrowMesssageSentException();
    return 0;
}
- (struct _NSZone *)zone{
    MIZombieThrowMesssageSentException();
    return  nil;
}
#pragma mark - private
- (void)_throwMessageSentExceptionWithSelector:(SEL)selector{
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"(-[%@ %@]) was sent to a zombie object at address: %p", NSStringFromClass(self.originClass),NSStringFromSelector(selector), self] userInfo:nil];
}
@end
  • hook free方法的具体实现
<!--1、MISafeFree.h-->
@interface MISafeFree : NSObject
//系统警告时,用函数释放一些内存
void free_safe_mem(size_t freeNum);
@end
<!--2、MISafeFree.m-->
#import "MISafeFree.h"
#import "queue.h"
#import "fishhook.h"
#import "MIZombieProxy.h"
#import <dlfcn.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
//用于保存zombie类
static Class kMIZombieIsa;
//用于保存zombie类的实例变量大小
static size_t kMIZombieSize;
//用于表示调用free函数
static void(* orig_free)(void *p);
//用于保存已注册的类的集合
static CFMutableSetRef registeredClasses = nil;
/*
 用来保存自己保留的内存
 - 1、队列要线程安全或者自己加锁
 - 2、这个队列内部应该尽量少申请和释放堆内存
 */
struct DSQueue *_unfreeQueue = NULL;
//用来记录自己保存的内存的大小
int unfreeSize = 0;
//最多存储的内存,大于这个值就释放一部分
#define MAX_STEAL_MEM_SIZE 1024*1024*100
//最多保留的指针个数,超过就释放一部分
#define MAX_STEAL_MEM_NUM 1024*1024*10
//每次释放时释放的指针数量
#define BATCH_FREE_NUM 100
@implementation MISafeFree
#pragma mark - Public Method
//系统警告时,用函数释放一些内存
void free_safe_mem(size_t freeNum){
#ifdef DEBUG
    //获取队列的长度
    size_t count = ds_queue_length(_unfreeQueue);
    //需要释放的内存大小
    freeNum = freeNum > count ? count : freeNum;
    //遍历并释放
    for (int i = 0; i < freeNum; i++) {
        //获取未释放的内存块
        void *unfreePoint = ds_queue_get(_unfreeQueue);
        //创建内存块申请的大小
        size_t memSize = malloc_size(unfreePoint);
        //原子减操作,多线程对全局变量进行自减
        __sync_fetch_and_sub(&unfreeSize, (int)memSize);
        //释放
        orig_free(unfreePoint);
    }
#endif
}
#pragma mark - Life Circle
+ (void)load{
#ifdef DEBUG
    loadZombieProxyClass();
    init_safe_free();
#endif
}
#pragma mark - Private Method
void safe_free(void* p){
    //获取自己保留的内存的大小
    int unFreeCount = ds_queue_length(_unfreeQueue);
    //保留的内存大于一定值时就释放一部分
    if (unFreeCount > MAX_STEAL_MEM_NUM*0.9 || unfreeSize>MAX_STEAL_MEM_SIZE) {
        free_safe_mem(BATCH_FREE_NUM);
    }else{
        //创建p申请的内存大小
        size_t memSize = malloc_size(p);
        //有足够的空间才覆盖
        if (memSize > kMIZombieSize) {
            //指针强转为id对象
            id obj = (id)p;
            //获取指针原本的类
            Class origClass = object_getClass(obj);
            //判断是不是objc对象
            char *type = @encode(typeof(obj));
            /*
             - strcmp 字符串比较
             - CFSetContainsValue 查看已注册类中是否有origClass这个类
             如果都满足,则将这块内存填充0x55
             */
            if (strcmp("@", type) == 0 && CFSetContainsValue(registeredClasses, origClass)) {
                //内存上填充0x55
                memset(obj, 0x55, memSize);
                //将自己类的isa复制过去
                memcpy(obj, &kMIZombieIsa, sizeof(void*));
                //为obj设置指定的类
                object_setClass(obj, [MIZombieProxy class]);
                //保留obj原本的类
                ((MIZombieProxy*)obj).originClass = origClass;
                //多线程下int的原子加操作,多线程对全局变量进行自加,不用理会线程锁了
                __sync_fetch_and_add(&unfreeSize, (int)memSize);
                //入队
                ds_queue_put(_unfreeQueue, p);
            }else{
                orig_free(p);
            }
        }else{
            orig_free(p);
        }
    }
}
//加载野指针自定义类
void loadZombieProxyClass(){
    registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
    //用于保存已注册类的个数
    unsigned int count = 0;
    //获取所有已注册的类
    Class *classes = objc_copyClassList(&count);
    //遍历,并保存到registeredClasses中
    for (int i = 0; i < count; i++) {
        CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
    }
    //释放临时变量内存
    free(classes);
    classes = NULL;
    kMIZombieIsa = objc_getClass("MIZombieProxy");
    kMIZombieSize = class_getInstanceSize(kMIZombieIsa);
}
//初始化以及free符号重绑定
bool init_safe_free(){
    //初始化用于保存内存的队列
    _unfreeQueue = ds_queue_create(MAX_STEAL_MEM_NUM);
    //dlsym 在打开的库中查找符号的值,即动态调用free函数
    orig_free = (void(*)(void*))dlsym(RTLD_DEFAULT, "free");
    /*
     rebind_symbols:符号重绑定
     - 参数1:rebindings 是一个rebinding数组,其定义如下
         struct rebinding {
           const char *name;  // 目标符号名
           void *replacement; // 要替换的符号值(地址值)
           void **replaced;   // 用来存放原来的符号值(地址值)
         };
     - 参数2:rebindings_nel 描述数组的长度
     */
    //重绑定free符号,让它指向自定义的safe_free函数
    rebind_symbols((struct rebinding[]){{"free", (void*)safe_free}}, 1);
    return true;
}
@end
  • 测试
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    self.assignObj = obj;
//    [MIZombieSniffer installSniffer];
}
- (IBAction)mallocScribbleAction:(id)sender {
    UIView* testObj = [[UIView alloc] init];
    [testObj release];
    for (int i = 0; i < 10; i++) {
        UIView* testView = [[UIView alloc] initWithFrame:CGRectMake(0,200,CGRectGetWidth(self.view.bounds), 60)];
        [self.view addSubview:testView];
    }
    [testObj setNeedsLayout];
}

打印结果如下

image.png


2、Zombie Objects


僵尸对象


  • 可以用来检测内存错误(EXC_BAD_ACCESS),它可以捕获任何阐释访问坏内存的调用
  • 给僵尸对象发送消息的话,它仍然是可以响应的,然后会发生崩溃,并输出错误日志来显示野指针对象调用的类名和方法


苹果的僵尸对象检测原理


首先我们来看下Xcode中僵尸对象是如何实现的,具体操作步骤可以参考这篇文章iOS Zombie Objects(僵尸对象)原理探索


  • dealloc的源码中,我们可以看到“Replaced by NSZombie”,即对象释放时, NSZombie 将在 dealloc 里做替换,如下所示

image.png

所以僵尸对象的生成过程伪代码如下

//1、获取到即将deallocted对象所属类(Class)
Class cls = object_getClass(self);
//2、获取类名
const char *clsName = class_getName(cls)
//3、生成僵尸对象类名
const char *zombieClsName = "_NSZombie_" + clsName;
//4、查看是否存在相同的僵尸对象类名,不存在则创建
Class zombieCls = objc_lookUpClass(zombieClsName);
if (!zombieCls) {
     //5、获取僵尸对象类 _NSZombie_
 Class baseZombieCls = objc_lookUpClass(“_NSZombie_");
     //6、创建 zombieClsName 类
 zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);
}
//7、在对象内存未被释放的情况下销毁对象的成员变量及关联引用。
   objc_destructInstance(self);
//8、修改对象的 isa 指针,令其指向特殊的僵尸类
objc_setClass(self, zombieCls);

当僵尸对象再次被访问时,将进入消息转发流程,开始处理僵尸对象访问,输出日志并发生crash

image.png

所以僵尸对象触发流程伪代码如下

//1、获取对象class
Class cls = object_getClass(self);
//2、获取对象类名
const char *clsName = class_getName(cls);
//3、检测是否带有前缀_NSZombie_
if (string_has_prefix(clsName, "_NSZombie_")) {
//4、获取被野指针对象类名
  const char *originalClsName = substring_from(clsName, 10);
 //5、获取当前调用方法名
 const char *selectorName = sel_getName(_cmd);
  
 //6、输出日志
 Log(''*** - [%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);
 //7、结束进程
 abort();

所以综上所述,这中野指针探测方式的思路是:dealloc方法的替换,其关键是调用objc_destructInstance 来解除对象的关联引用


野指针探测实现2


这种方式的思路主要是来源sindrilin的源码,其主要思路是:


  • 野指针检测流程


  • 1、开启野指针检测
  • 2、设置监控到野指针时的回调block,在block中打印信息,或者存储堆栈
  • 3、检测到野指针是否crash
  • 4、最大内存占用空间
  • 5、是否记录dealloc调用栈
  • 6、监控策略
  • 1)只监控自定义对象
  • 2)白名单策略
  • 3)黑名单策略
  • 4)监控所有对象
  • 7、交换NSObject的dealloc方法


  • 触发野指针


  • 1、开始处理对象
  • 2、是否达到替换条件
  • 1)根据监控策略,是否属于要检测的类
  • 2)空间是否足够
  • 3、如果符合条件,则获取对象,并解除引用,如果不符合则正常释放,即调用原来的dealloc方法
  • 4、向对象内填充数据
  • 5、赋值僵尸对象的类指针替换isa
  • 6、对象+dealloc调用栈,保存在僵尸对象中
  • 7、根据情况是否清理内存和对象


通过僵尸对象检测的实现思路


  • 1、通过OC中Mehod Swizzling,交换根类NSObject和NSProxydealloc方法为自定义的dealloc方法
  • 2、为了避免内存空间释放后被重写造成野指针的问题,通过字典存储被释放的对象,同时设置在30s后调用dealloc方法将字典中存储的对象释放,避免内存增大
  • 3、为了获取更多的崩溃信息,这里同样需要创建NSProxy的子类


具体实现


  • 1、创建NSProxy的子类,其实现与上面的MIZombieProxy是一模一样的
  • 2、hook dealloc函数的具体实现
<!--1、MIZombieSniffer.h-->
@interface MIZombieSniffer : NSObject
/*!
 *  @method installSniffer
 *  启动zombie检测
 */
+ (void)installSniffer;
/*!
 *  @method uninstallSnifier
 *  停止zombie检测
 */
+ (void)uninstallSnifier;
/*!
 *  @method appendIgnoreClass
 *  添加白名单类
 */
+ (void)appendIgnoreClass: (Class)cls;
@end
<!--2、MIZombieSniffer.m-->
#import "MIZombieSniffer.h"
#import "MIZombieProxy.h"
#import <objc/runtime.h>
//
typedef void (*MIDeallocPointer) (id objc);
//野指针探测器是否开启
static BOOL _enabled = NO;
//根类
static NSArray *_rootClasses = nil;
//用于存储被释放的对象
static NSDictionary<id, NSValue*> *_rootClassDeallocImps = nil;
//白名单
static inline NSMutableSet *__mi_sniffer_white_lists(){
    //创建白名单集合
    static NSMutableSet *mi_sniffer_white_lists;
    //单例初始化白名单集合
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mi_sniffer_white_lists = [[NSMutableSet alloc] init];
    });
    return mi_sniffer_white_lists;
}
static inline void __mi_dealloc(__unsafe_unretained id obj){
    //获取对象的类
    Class currentCls = [obj class];
    Class rootCls = currentCls;
    //获取非NSObject和NSProxy的类
    while (rootCls != [NSObject class] && rootCls != [NSProxy class]) {
        //获取rootCls的父类,并赋值
        rootCls = class_getSuperclass(rootCls);
    }
    //获取类名
    NSString *clsName = NSStringFromClass(rootCls);
    //根据类名获取dealloc的imp指针
    MIDeallocPointer deallocImp = NULL;
    [[_rootClassDeallocImps objectForKey:clsName] getValue:&deallocImp];
    if (deallocImp != NULL) {
        deallocImp(obj);
    }
}
//hook交换dealloc
static inline IMP __mi_swizzleMethodWithBlock(Method method, void *block){
    /*
     imp_implementationWithBlock :接收一个block参数,将其拷贝到堆中,返回一个trampoline
     可以让block当做任何一个类的方法的实现,即当做类的方法的IMP来使用
     */
    IMP blockImp = imp_implementationWithBlock((__bridge id _Nonnull)(block));
    //method_setImplementation 替换掉method的IMP
    return method_setImplementation(method, blockImp);
}
@implementation MIZombieSniffer
//初始化根类
+ (void)initialize
{
    _rootClasses = [@[[NSObject class], [NSProxy class]] retain];
}
#pragma mark - public
+ (void)installSniffer{
    @synchronized (self) {
        if (!_enabled) {
            //hook根类的dealloc方法
            [self _swizzleDealloc];
            _enabled = YES;
        }
    }
}
+ (void)uninstallSnifier{
    @synchronized (self) {
        if (_enabled) {
            //还原dealloc方法
            [self _unswizzleDealloc];
            _enabled = NO;
        }
    }
}
//添加百名单
+ (void)appendIgnoreClass:(Class)cls{
    @synchronized (self) {
        NSMutableSet *whiteList = __mi_sniffer_white_lists();
        NSString *clsName = NSStringFromClass(cls);
        [clsName retain];
        [whiteList addObject:clsName];
    }
}
#pragma mark - private
+ (void)_swizzleDealloc{
    static void *swizzledDeallocBlock = NULL;
    //定义block,作为方法的IMP
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzledDeallocBlock = (__bridge void *)[^void(id obj) {
            //获取对象的类
            Class currentClass = [obj class];
            //获取类名
            NSString *clsName = NSStringFromClass(currentClass);
            //判断该类是否在白名单类
            if ([__mi_sniffer_white_lists() containsObject: clsName]) {
                //如果在白名单内,则直接释放对象
                __mi_dealloc(obj);
            } else {
                //修改对象的isa指针,指向MIZombieProxy
                /*
                 valueWithBytes:objCType  创建并返回一个包含给定值的NSValue对象,该值会被解释为一个给定的NSObject类型
                 - 参数1:NSValue对象的值
                 - 参数2:给定值的对应的OC类型,需要使用编译器指令@encode来创建
                 */
                NSValue *objVal = [NSValue valueWithBytes: &obj objCType: @encode(typeof(obj))];
                //为obj设置指定的类
                object_setClass(obj, [MIZombieProxy class]);
                //保留对象原本的类
                ((MIZombieProxy *)obj).originClass = currentClass;
                //设置在30s后调用dealloc将存储的对象释放,避免内存空间的增大
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    __unsafe_unretained id deallocObj = nil;
                    //获取需要dealloc的对象
                    [objVal getValue: &deallocObj];
                    //设置对象的类为原本的类
                    object_setClass(deallocObj, currentClass);
                    //释放
                    __mi_dealloc(deallocObj);
                });
            }
        } copy];
    });
    //交换了根类NSObject和NSProxy的dealloc方法为originalDeallocImp
    NSMutableDictionary *deallocImps = [NSMutableDictionary dictionary];
    //遍历根类
    for (Class rootClass in _rootClasses) {
        //获取指定类中dealloc方法
        Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc"));
        //hook - 交换dealloc方法的IMP实现
        IMP originalDeallocImp = __mi_swizzleMethodWithBlock(oriMethod, swizzledDeallocBlock);
        //设置IMP的具体实现
        [deallocImps setObject: [NSValue valueWithBytes: &originalDeallocImp objCType: @encode(typeof(IMP))] forKey: NSStringFromClass(rootClass)];
    }
    //_rootClassDeallocImps字典存储交换后的IMP实现
    _rootClassDeallocImps = [deallocImps copy];
}
+ (void)_unswizzleDealloc{
    //还原dealloc交换的IMP
    [_rootClasses enumerateObjectsUsingBlock:^(Class rootClass, NSUInteger idx, BOOL * _Nonnull stop) {
        IMP originDeallocImp = NULL;
        //获取根类类名
        NSString *clsName = NSStringFromClass(rootClass);
        //获取hook后的dealloc实现
        [[_rootClassDeallocImps objectForKey:clsName] getValue:&originDeallocImp];
        NSParameterAssert(originDeallocImp);
        //获取原本的dealloc实现
        Method oriMethod = class_getInstanceMethod([rootClass class], NSSelectorFromString(@"dealloc"));
        //还原dealloc的实现
        method_setImplementation(oriMethod, originDeallocImp);
    }];
    //释放
    [_rootClassDeallocImps release];
    _rootClassDeallocImps = nil;
}
@end
  • 3、测试
@interface ViewController ()
@property (nonatomic, assign) id assignObj;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    self.assignObj = obj;
    [MIZombieSniffer installSniffer];
}
- (IBAction)zombieObjectAction:(id)sender {
    NSLog(@"%@", self.assignObj);
}

打印崩溃信息如下

image.png


相关文章
|
2月前
|
监控 算法 Java
Java内存管理:垃圾收集器的工作原理与调优实践
在Java的世界里,内存管理是一块神秘的领域。它像是一位默默无闻的守护者,确保程序顺畅运行而不被无用对象所困扰。本文将带你一探究竟,了解垃圾收集器如何在后台无声地工作,以及如何通过调优来提升系统性能。让我们一起走进Java内存管理的迷宫,寻找提高应用性能的秘诀。
|
12天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
2月前
|
存储 安全 Go
Go 中的指针:了解内存引用
Go 中的指针:了解内存引用
|
2月前
|
Swift iOS开发
iOS开发-属性的内存管理
【8月更文挑战第12天】在iOS开发中,属性的内存管理至关重要,直接影响应用性能与稳定性。主要策略包括:`strong`(强引用),不维持对象生命期,可用于解除循环引用;`assign`(赋值),适用于基本数据类型及非指针对象属性;`copy`,复制对象而非引用,确保对象不变性。iOS采用引用计数管理内存,ARC(自动引用计数)自动处理引用增减,简化开发。为避免循环引用,可利用弱引用或Swift中的`[weak self]`。最佳实践包括:选择恰当的内存管理策略、减少不必要的强引用、及时释放不再使用的对象、注意block内存管理,并使用Xcode工具进行内存分析。
|
2月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
3月前
|
存储 算法 Linux
操作系统中的内存管理:从原理到实践
本文深入探讨了操作系统中至关重要的内存管理机制,揭示了从理论到实现的复杂过程。通过分析内存分配、虚拟内存以及分页和交换等概念,本篇文章旨在为读者提供对现代操作系统内存管理技术的全面理解。结合最新的技术动态和研究成果,文章不仅阐述了内存管理的基本原理,还讨论了其在实际操作系统中的应用和优化策略。
49 1
|
3月前
|
算法 Linux 调度
操作系统中的虚拟内存管理:原理与实现
本文深入探讨了操作系统中虚拟内存管理的核心概念,包括分页、分段、需求分页和页面置换算法。通过分析现代操作系统如Linux和Windows的虚拟内存实现机制,文章揭示了虚拟内存在提升内存利用率、进程隔离和保护内存中的关键作用。同时,讨论了虚拟内存管理面临的挑战,如内存泄漏、碎片化以及性能开销,并提出了相应的优化策略。
|
3月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
47 1
|
3月前
|
存储 缓存 Java
(一) 玩命死磕Java内存模型(JMM)与 Volatile关键字底层原理
文章的阐述思路为:先阐述`JVM`内存模型、硬件与`OS`(操作系统)内存区域架构、`Java`多线程原理以及`Java`内存模型`JMM`之间的关联关系后,再对`Java`内存模型进行进一步剖析,毕竟许多小伙伴很容易将`Java`内存模型(`JMM`)和`JVM`内存模型的概念相互混淆,本文的目的就是帮助各位彻底理解`JMM`内存模型。
|
3月前
|
存储 Linux Windows
操作系统中的内存管理:从原理到实践
内存管理是操作系统中的核心功能,它直接影响着系统的性能和稳定性。本文将深入探讨内存管理的基本原理、关键技术以及实际应用,帮助读者更好地理解内存管理在操作系统中的重要性。