强大的windbg定位内存泄露,两句命令搞定!

MLeaksFinder

  一谈到内存泄露, 多数程序员都闻之色变。 没错, 内存泄露很容易引入,
但很难定位。  以你我的手机为例(假设不经常关机),
如果每天泄露一些内存, 那么开始的一个星期, 你会发现手机好好的,
当内存泄露积累到一定程度,  那就是各种卡死了, 系统异常, 最后死机,
不得不重启。

以下原代码是使用MLeaksFinder定位到报错的代码,纠正后为不存在泄露的代码

        如果搞开发, 遇到内存泄露问题, 那就呵呵了。
你可能先得花好几天来复现问题(泄露积累),
然后需要花好几天来定位问题和修改问题, 然后又要花好几天来验证问题,
而且, 很有可能没法一次改好, 上述流程又要循环了。 确实挺苦逼的。

  • 原代码一:

      UIButton *btn = [self createBtnWithTitle:title];
      /*! 监听点击事件 */
      [btn bk_whenTapped:^{
          int tag = (int)btn.tag;
          self.selectedBtnType = tag;
          self.clicked(tag);
      }];
    

        我个人认为, 在内存泄露问题上, 主动预防比被动定位要划算得多,
但无论你怎么预防, 总有掉链子的时候, 所以,
有时候不得不去被动定位内存泄露。

纠正后:

 

    int tag = (int)btn.tag;
    /*! 监听点击事件 */
    __weak typeof(self) weakSelf = self;
    [btn bk_whenTapped:^{
        weakSelf.selectedBtnType = tag;
        weakSelf.clicked(tag);
    }];

        在本文中, 暂不谈论手机内存泄露问题的定位,
仅仅介绍一个有用的linux小命令:mtrace(memory trace),
它可以用来协助定位内存泄露。 搞开发的, 应该或多或少地听说过mtrace.

  • 原代码二:

     [_shareBtn bk_whenTapped:^{
          _shareClicked();
      }];
    

        下面, 我们来看看程序:

纠正后:

[cpp] view
plain copy

     __weak typeof(self) weakSelf = self;
    [_shareBtn bk_whenTapped:^{
        weakSelf.shareClicked();
    }];

 

  • 原代码三:
  1. #include <stdio.h>  
  2.   
  3. int main()  
  4. {  
  5.         setenv(“MALLOC_TRACE”, “taoge.log”, “1”);  
  6.         mtrace();  
  7.   
  8.         int *p = (int *)malloc(2 * sizeof(int));  
  9.   
  10.         return 0;  
  11. }  

       有的朋友要说了, 一眼就能看出内存泄露啊。 但是, 当程序大了之后,
怎能仅仅依靠肉眼? 好, mtrace该出场了。

 [_rightBackBtn bk_whenTapped:^{
        [self.viewController.navigationController popViewControllerAnimated:YES];
    }];

 

纠正后:

 

 [_rightBackBtn bk_whenTapped:^{
        [weakSelf.viewController.navigationController popViewControllerAnimated:YES];
    }];

        编译:gcc -g -DDEBUG test.c   (千万要注意, -g不可漏掉, 否则,
虽然最后能定位到内存泄露,
但却找不到在代码的第几行。由于我代码中没有Debug宏控制, 所以编译时,
-DDEBUG是可以省略的, 因此, 直接写成gcc -g test.c即可)

  • 原代码四:

        运行:./a.out

        定位:mtrace a.out taoge.log

 __weak typeof(self) weakSelf = self;
[propertyView addObserverBlockForKeyPath:@"selectedSku" block:^(__weak id obj, ALGoodsSKUItem *oldItem, ALGoodsSKUItem *newItem) {
    if (![newItem.SKUID isEqualToString:oldItem.SKUID]) {
            _currShowSkuItem = newItem;
            weakSelf.requestActivityData();
    }
}];

        结果:

纠正后:

图片 1

  __weak typeof(self) weakSelf = self;
[propertyView addObserverBlockForKeyPath:@"selectedSku" block:^(__weak id obj, ALGoodsSKUItem *oldItem, ALGoodsSKUItem *newItem) {
    if (![newItem.SKUID isEqualToString:oldItem.SKUID]) {
            weakSelf.currShowSkuItem = newItem;
            weakSelf.requestActivityData();
    }
}];

       可以看到, 有内存泄露,且正确定位到了代码的行数。

  • 原代码五:

 

       我们想一下mtrace函数/命令的原理, 其实也很简单,
无非就是记录每一对malloc/free的调用情况, 从这个意义上来讲,
mtrace替代了部分我们的眼睛, 紧紧地盯着malloc/free,
所以能看到泄露还是不泄露啊。

/*! 监听address的frame,当需要显示身份视图的时候修改其他视图的位置 */
/*! headerTitleView/headerView/headerTitleView/addressView 都是局部对象变量 */
__weak typeof(self) weakSelf = self;
[self.addressView addObserverBlockForKeyPath:@"frame" block:^(__weak id obj, id oldVal, id newVal) {
    headerTitleView.top = addressView.bottom;
    headerView.height = headerTitleView.bottom;
    weakSelf.tableView.tableHeaderView = headerView;
}];

 

纠正后:

 

/*! 监听address的frame,当需要显示身份视图的时候修改其他视图的位置 */
/*! headerTitleView/headerView/headerTitleView/addressView 都是局部对象变量 */
__weak typeof(self) weakSelf = self;
__weak typeof(headerTitleView) weakHeaderTitleView = headerTitleView;
__weak typeof(addressView) weakAddressView = addressView;
__weak typeof(headerView) weakHeaderView = headerView;
[self.addressView addObserverBlockForKeyPath:@"frame" block:^(__weak id obj, id oldVal, id newVal) {
    weakHeaderTitleView.top = weakAddressView.bottom;
    weakHeaderView.height = weakHeaderTitleView.bottom;
    weakSelf.tableView.tableHeaderView = weakHeaderView;
}];

       说明一下, 我的linux上并没有安装mtrace命令, 所以无法调试,
在网友Jukay的帮助下, 我才接触到shiyanlou这个优秀的在线工具,
地址是: , 大家不需要注册, 直接用QQ登录即可,
上面的过程就是在shiyanlou中做的。 没有linux环境的朋友们,
以后就可以在这上面玩了, 不要再扯理由说没有linux环境啦。
再次感谢Jukay介绍这么优秀的在线工具。

总结:这个地方我调试了很久,有用过__block来修饰局部对象,不起作用,这里贴上__block和__weak的区别

 

*__block和__weak的区别:
1.在MRC环境下,__block是弱引用,在ARC环境下是强引用
2.__block本身无法避免循环引用的问题,但是我们可以通过在 block
内部手动把 blockObj 赋值为 nil
的方式来避免循环引用的问题。另外一点就是 __block修饰的变量在 block
内外都是唯一的,要注意这个特性可能带来的隐患。
3.__weak本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block
内部也访问不到这个对象的问题,我们可以通过在 block
内部声明一个strong的变量来指向 weakObj,使外部对象既能在 block
内部保持住,又能避免循环引用的问题。

       OK,  本文先写到这里,
后续会继续介绍一些与linux有关的基本调试工具和方法。

  • 原代码五:
    _mainView = self.superview; /*! _mainView为成员变量 */

 

纠正后:

去掉_mainView的变量,用到_mainView的用self.superview代替就解决了

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图