内存泄漏和僵尸对象的区别与联系

0 阅读2分钟

内存泄漏(Memory Leak)和僵尸对象(Zombie Object)的区别与联系


内存泄漏 vs 僵尸对象

方面内存泄漏(Memory Leak)僵尸对象(Zombie Object)
定义程序中已不再使用的对象没有被释放,仍占用内存对象已经被释放,但程序仍尝试访问该对象的内存区域
本质原因对象的引用计数没有归零,导致内存无法回收访问已释放对象的指针,导致野指针访问
表现形式内存持续增长,最终可能导致内存耗尽程序崩溃,通常是 EXC_BAD_ACCESS 或访问违规错误
发生时机运行过程中未正确释放对象释放对象后继续访问该对象
调试方式使用 Instruments 的 Leaks 工具、静态分析等开启 Zombie 模式,使用 Instruments 的 Zombies 工具
解决方法及时释放对象,避免循环引用,管理好对象生命周期不访问已释放对象,修正野指针,确保对象访问安全

联系

  • 内存泄漏和僵尸对象都与内存管理相关,都是因对象生命周期管理不当引起的问题。

  • 两者都是内存错误,但表现和后果不同:

    • 内存泄漏是“没释放”,导致内存浪费。
    • 僵尸对象是“访问已释放”,导致程序崩溃。
  • 有时内存泄漏会导致程序长期占用大量内存,影响性能;僵尸对象通常导致崩溃,影响稳定性。

  • 在调试过程中,二者常常结合使用不同工具定位问题。


简单比喻

  • 内存泄漏:就像你租了一个房间,但搬走后忘了退租,房间一直占着,别人用不了。
  • 僵尸对象:就像你退了房,但钥匙还在别人手里,别人试图进房间发现房间已经不存在了,导致混乱。

示例代码

  • 内存泄漏示例(NSTimer 循环引用)

    @interface MyClass ()
    @property (nonatomic, strong) NSTimer *timer;
    @end@implementation MyClass
    ​
    - (void)startTimer {
        // NSTimer 强引用 self,self 又强引用 timer,形成循环引用
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0                                                  target:self                                                selector:@selector(timerFired)                                                userInfo:nil                                                 repeats:YES];
    }
    ​
    - (void)timerFired {
        NSLog(@"Timer fired");
    }
    ​
    - (void)dealloc {
        [self.timer invalidate]; 
        NSLog(@"MyClass dealloc");
    }
    ​
    @end
    
  • 僵尸对象示例(访问已释放对象)

    interface MyObject : NSObject
    @end@implementation MyObject
    - (void)dealloc {
        NSLog(@"MyObject dealloc");
    }
    @endvoid testZombie() {
        MyObject *obj = [[MyObject alloc] init];
        [obj release]; // 手动释放对象(非 ARC 环境下)
        // 下面访问已释放的对象,导致僵尸对象崩溃
        [obj description]; // EXC_BAD_ACCESS 错误
    }