知其然并知其所以然,从基础开始,我们深入观察各种酷炫技术的内在实现


java 排查内存泄露 java如何排查内存泄漏_解决方案



在我们写一个庞大项目的时候,比如我们为公司写一个面向具体业务的引擎内核的时候,我们并没有什么卵详尽的开发计划和设计,往往我们都是一边设计,一边想实现,需求还经常变化,这是无法避免的,就像是你去打副本,你只知道要把人家Boss收拾了才能拿到装备,至于你每次去的时候组到了什么队友,怎么收拾的Boss,那可能就千变万化了。 从设计者的角度来看 这种开发流程是非常棒的

java 排查内存泄露 java如何排查内存泄漏_java 排查内存泄露_02


因为非常的自由与灵活 碰到问题随机应变、灵活处置 但是从施工队的角度来说 这必然会导致项目变得一团糟 胸中除了羊驼在奔腾以外 杀人的心都有了

java 排查内存泄露 java如何排查内存泄漏_解决方案_03


当项目的dead line临近的时候,我们忽然开始意识到,我们需要把代码整理一下,把没用的代码删掉,让项目变得稳定起来,要开始准备Alpha测试了。 如果你是在一个小公司或者大公司里的一个小团队的话,至少在一般的游戏公司中,依然是大公司下圈养着一群互相独立的小作坊的模式。如此一来,你就不可能指望能拿到一大堆专业的测试工具来帮助你把项目测试一遍。于是我们就需要一个解决方案,这个解决方案能够很轻松的植入到现有的项目代码中,同样,我们希望这样的一个解决方案是通用的,而不是说我们需要为每一个项目都搞一套内存泄漏检查方案。我们希望这样的一套内存检测机制能够非常容易的在下一次项目中也能用上。在程序运行结束的时候,我能够通过这个解决方案拿到程序未释放的内存的一个清单,这样就可以帮助我去排查内存的泄漏了。 已经报名了我们引擎课程的同学应该是想到了,我们讲过的内存池的实现,其实那个不仅仅是实现了内存池来加速内存的分配和回收速度,更大的作用在于帮助你去非常方便的去Debug你项目的内存的使用状况。

java 排查内存泄露 java如何排查内存泄漏_解决方案_04


系统工程-复杂系统的内存管理层 现在让我来更详尽的阐明,我们的内存追踪器到底需要什么。首先,我们需要这个内存追踪器能够添加到现有的项目中,且不要对现有的项目进行大的改动。代码重用是非常重要的,尤其是你在一个公司里面干活的时候。它可能节省很多时间与金钱。其次,我们的解决方案必须足够简单。我们不能够为了实现对项目中所有内存使用的追踪而对现有的代码大改特改。最后这个解决方案必须是免费的

java 排查内存泄露 java如何排查内存泄漏_解决方案_05


虽然大家都说免费的东西是最贵的 所以我们现在来看一眼我们项目里的代码,我们发现的第一个东西就是,所有的内存分配的操作都是通过new操作符以及它的变种来完成的。同样的,所有的内存回收函数都是通过delete操作符以及它的变种来完成的。 那么我们是不是只需要使用什么专门的函数来替换掉现在的这些new和delete操作符就可以完成追踪内存的目的了呢? 不是的!这么干,你要改动的代码太多了。C++允许你去为你的类重载new和delete操作符。如果这不是意味着你需要为你的所有的类去重载new和delete运算符的话,这将是一个非常棒的解决方案。等等...我们也可以重载全局的new和delete操作的啊!!!重载了全局的new和delete操作符了之后,你就可以在每一次分配内存和释放内存的时候干任何你想干的事情了。这真的是太棒了!其实我们的引擎同学应该已经学完这块了,毕竟这是整个引擎的根基,我们教会同学们的岂止是一个重载new和delete的操作符,还有我们的C++Tricks中讲述的,我们是在教会同学们整套底层的内核级玩法。 言归正传,现在我们只需要在内存块被逻辑层需要的时候标记一下,如果程序运行结束的时候,这块内存没被还回来,那么它就是泄漏了! 由于我们这里不是在实现内存池管理解决方案,所以我们这里加了_DEBUG宏,引擎课程学习的同学可以不必管这里讲的这些,跟着引擎课的内容走就可以了,那边是整套成体系的内容。

#ifdef _DEBUGinline void * __cdecl operator new(unsigned int size, const char *file, int line){};inline void __cdecl operator delete(void *p){};#endif

上面就是现阶段的全部代码了,我们就重载了这俩函数,并且我们用#ifdef和#endif把它们框起来了,因为我们说了,我们本文介绍的是内存追踪,不是内存池算法。如果你实现了内存池算法,那么你的这个重载应该是一直开着的。内存池的好处咱们就不详细的说了,总而言之,就是让你的系统更加的稳定和可Debug。

由于我们还希望知道我们内存分配的时候,是哪个文件的哪行代码分配的内存泄漏了,所以我们的使用了下面的宏来进一步的封装了new运算符,这样一来,你就无需要改变你现有的代码,它们会自动的给自己添加上自己是在哪个地方初始化的内存块。

#ifdef _DEBUG#define DEBUG_NEW new(__FILE__, __LINE__)#else#define DEBUG_NEW new#endif#define new DEBUG_NEW

现在我们来实现我们的new和delete函数,我们在初始化内存块后,对它进行追踪,在释放内存块之后,取消对它的追踪,如此一来,程序运行结束之后,还处于追踪状态的内存块就是没被释放的内存块,也就是内存泄漏了。

#ifdef _DEBUGinline void * __cdecl operator new(unsigned int size,const char *file, int line){    void *ptr = (void *)malloc(size);    AddTrack((DWORD)ptr, size, file, line);    return(ptr);};inline void __cdecl operator delete(void *p){    RemoveTrack((DWORD)p);    free(p);};#endif

下面的代码展示的就是追踪内存块和取消对内存块追踪的代码,我们会把所有的内存块都串成一个链表。

typedef struct {  DWORD  address;  DWORD  size;  char  file[64];  DWORD  line;} ALLOC_INFO;typedef list AllocList;AllocList *allocList;void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum){  ALLOC_INFO *info;  if(!allocList) {    allocList = new(AllocList);  }  info = new(ALLOC_INFO);  info->address = addr;  strncpy(info->file, fname, 63);  info->line = lnum;  info->size = asize;  allocList->insert(allocList->begin(), info);};void RemoveTrack(DWORD addr){  AllocList::iterator i;  if(!allocList)    return;  for(i = allocList->begin(); i != allocList->end(); i++){    if((*i)->address == addr){      allocList->remove((*i));      break;    }  }};

最后这段代码就是在程序运行结束的时候去打印出来还没有被释放的内存块的代码,这些内存块就是内存泄漏的地方。由于我们之前记录了每一块内存是从哪里分配出去的,所以我们很容易就知道,我们的代码是哪里出现了内存泄漏。

void DumpUnfreed(){  AllocList::iterator i;  DWORD totalSize = 0;  char buf[1024];  if(!allocList)    return;  for(i = allocList->begin(); i != allocList->end(); i++) {    sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",      (*i)->file, (*i)->line, (*i)->address, (*i)->size);    OutputDebugString(buf);    totalSize += (*i)->size;  }  sprintf(buf, "-----------------------------------------------------------\n");  OutputDebugString(buf);  sprintf(buf, "Total Unfreed: %d bytes\n", totalSize);  OutputDebugString(buf);};

故事的最后

我们为同学们介绍一个程序分析工具

这个工具的名字叫:valgrind

它的功能是自动检测内存管理

以及线程BUG

官网地址如下:

http://www.valgrind.org/