编程时与内存相关问题的总结(内存碎片、内存泄漏等)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Dachao0707/article/details/82387107

1、程序所占用系统内存分为哪几个部分

代码段:二进制的指令

只读段:字符串字面值、常量

全局数据段:初始化的静态变量、全局变量

静态数据段bss段:末初始化的静态变量、全局变量,在程序执行时会被清理为0。堆:由程序员管理 足够大(理论上可以是物理内存的极限),数据的释放受控制,可能会产生内存泄漏和内存碎片。

栈:由操作系统管理 大小有限(栈崩溃),数据的释放是不受控制的,栈内存几乎不会产生什么管理上的错误。

命令行、环境变量表:程序运行附加的数据

2、什么是内存虚拟内存

1、每个进程都会有独立虚拟内存空间(4G)。

2、进程永远只能使用虚拟内存、无法直接访问物理内存

3、虚拟内存不能直接使用,必须要与物理内存建立映射关系才能使用,否会产生段错误。

4、虚拟内存与物理内存的映射由操作系统动态维护。

5、使用虚拟内存<映射>物理内存的好处:

1、保障了操作系统的安全

2、可以使用超过4G

3、大于实际物理的内存(使用硬盘文件来模拟内存)。

6、0G~3G的虚拟地址属于用户,3G~4G的虚拟地址属于内核。

7、用户态的程序不能直接访问内核中的数据,必须经过系统调用进入内核态,当系统调用执行完毕后,再把数据分享给用户。

8、进程之间的内存地址是相互独立的(进程之间如果要协同工作必须要解决通信的问题)。

3、什么是内存映射

malloc:首次使用malloc申请内存时,malloc会向操作系统请求建映射关系,操作系统会帮malloc映射33页的内存,交给malloc管理,如果33页内存分配使用完了,操作系统会再分配33页。

只要映射过的内存就不会产生段错误,但是可能会破坏掉malloc的维护信息行造成接下来的申请或释放错误,也有可能产生脏数据。

清理内存:bzero strings.h/memset string.h

calloc 以nmemb*size的方式申请内存,而且会把内存清理为0。

realloc 增/减已有的内存,如果第一个参数为NULL也可以用来申请内存。

free 释放内存,记得指针置为NULL

alloc 分配当前函数的栈内存,当函数结束后会自动释放,只有部分操作系统支持。

一页内存=默认是4096byte getpagesize可以获取一页内存的大小。

brk/sbrk

POXIX标准定义的系统内存管理函数,它们共同管理一个内存末尾指针,brk和sbrk都具有内存的申请和释放功能,但他们两个配合使用比较方便。

mmap/munmap

Linux系统实现的内存管理函数,brk/sbrk就调用经它们,它不光可以对虚拟地址与物理内存进行映射,还可以对虚拟地址和文件进行映射(磁盘文件虚拟成内存使用)。

注意:brk/sbrk、mmap/munmap都是以页为单位。

4、什么是内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

在内存中供用户使用的内存空间分为三部分:程序存储区静态存储区动态存储区。

一般情况下,开发人员使用系统提供的内存管理基本函数,如malloc、recalloc、calloc、free等,完成动态存储变量存储空间的分配和释放。但是,当开发程序中使用动态存储变量较多和频繁使用函数调用时,就会经常发生内存管理错误,例如:

1、分配一个内存块并使用其中未经初始化的内容

2、释放一个内存块,但继续引用其中的内容;

3、子函数中分配的内存空间在主函数出现异常中断时、或主函数对子函数返回的信息使用结束时,没有对分配的内存进行释放;

4、程序实现过程中分配的临时内存在程序结束时,没有释放临时内存。内存错误一般是不可再现的,开发人员不易在程序调试和测试阶段发现,即使花费了很多精力和时间,也无法彻底消除。

 

产生内存泄漏方式的分类:

以产生的方式来分类,内存泄漏可以分为四类:

  1. 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。
  2. 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

4、隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。

5、什么是内存碎片,如何减少内存碎片

  内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。产生内存碎片的方法很简单,举个例: 
    假设有一块一共有100个单位的连续空闲内存空间,范围是0~99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。这时候你继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10~14区间。 
    如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。 
    现在整个内存空间的状态是0~9空闲,10~14被占用,15~24被占用,25~99空闲。其中0~9就是一个内存碎片了。如果10~14一直被占用,而以后申请的空间都大于10个单位,那么0~9就永远用不上了,造成内存浪费。 
    如果你每次申请内存的大小,都比前一次释放的内村大小要小,那么申请就总能成功

减少内存碎片:

大体思路自己编写内存管理模块,程序一开始就申请一大块内存(内存池),然后以后申请内存都在这个大内存中取,配合一定的技巧来减少内存碎片问题。在C++中我们可以重写operator new/operator delete来减少内存碎片出现的机会,

一、重写new和delete


    //FreeListBase.h  

    #define NULL 0  

    class FreeListBase  

    {  

    public:  

        FreeListBase(void){}  

        virtual ~FreeListBase(void){}  

    public:  

        void* operator new(size_t size);  

        void operator delete(void* p,size_t size);  

    private:  

        static FreeListBase* freelist;  

        FreeListBase* next;  

    };  

    //FreeListBase.cpp  

    void* FreeListBase::operator new(size_t size)  

    {  

        if(freelist != NULL)  

        {  

            FreeListBase* p = freelist;  

            freelist = freelist->next;  

            return p;  

        }  

        else  

            return ::operator new(size);  

    }  

    void FreeListBase::operator delete(void* vp,size_t size)  

    {  

        FreeListBase* p = static_cast<FreeListBase*>(vp);  

        p->next = freelist;  

        freelist = p;  

    }  

    //TemperatureUsingFreeList.h  

    #include "freelistbase.h"  

    class TemperatureUsingFreeList :  

        public FreeListBase  

    {  

    public:  

        TemperatureUsingFreeList(void){}  

        ~TemperatureUsingFreeList(void){}  

        inline int average() { return (maxTemp + minTemp)/2; }  

    private:  

        int ID;  

        int maxTemp;  

        int minTemp;  

        int currentTemp;  

    };   

二、其他做法,大体思路是一样的,都是先开辟一块大的内存,再进行内存管理。


    //BigChunkStack.H  

    #define NULL 0  

    #define INITSIZE 30  

    class BigChunkStack  

    {  

        struct elem  

        {  

            int id;                   

            int previousElemSize;    //上一个元素的大小  

            int namesize;            //name的大小  

            char* name;  

        };  

    public:  

        BigChunkStack(void);  

        ~BigChunkStack(void);  

    public:  

        void push(const char* s,const int nr);  

        void pop(char* s,int& nr);  

        int grow();  

        int shrink();  

    private:  

        int totalSize;  

        int emptyElemSize;  

        int lastElemSize;     //最后一个加入的元素的大小  

        char* pool;           //内存池指针  

        int MAXSIZE;          //内存池的大小  

    };  

    //BigChunkStack.cpp  

    BigChunkStack::BigChunkStack(void)  

    {  

        totalSize = 0;  

        emptyElemSize = sizeof(elem);  

        lastElemSize = 0;  

        pool = NULL;  

        MAXSIZE = 0;  

    }  

    BigChunkStack::~BigChunkStack(void)  

    {  

    }  

    void BigChunkStack::push(const char* s,const int nr)  

    {  

        assert(s != NULL);  

        int newStringSize = strlen(s) + 1;  

        int newElemSize = newStringSize + emptyElemSize;  

        if((totalSize + newElemSize) > MAXSIZE)  

        {  

            if(!grow())  

            {  

                cerr<<"Error,Stack Overflow!"<<endl;  

                return ;  

            }  

        }  

        elem* newElem = (elem*)(pool+totalSize);  

        newElem->name = (char*)(pool+totalSize+emptyElemSize);  

        newElem->id = nr;  

        newElem->namesize = newStringSize;  

        newElem->previousElemSize = lastElemSize;  

        strcpy(newElem->name,s);  

        lastElemSize = newElemSize;  

        totalSize += newElemSize;  

    }  

    void BigChunkStack::pop(char* s,int& nr)  

    {  

        if(totalSize*4 <= MAXSIZE)  

            shrink();  

        if(totalSize != 0)  

        {  

            totalSize -= lastElemSize;  

            elem* popElem = (elem*)(pool+totalSize);  

            lastElemSize = popElem->previousElemSize;  

            strcpy(s,popElem->name);  

            nr = popElem->id;  

        }  

        else  

        {  

            cerr<<"Error,Stack Underflow!!"<<endl;  

        }  

    }  

    int BigChunkStack::grow()  

    {  

        if(MAXSIZE == 0)  

        {  

            pool = new char[INITSIZE];  

            if(pool == NULL)  

                return 0;  

            MAXSIZE = INITSIZE;  

            return 1;  

        }  

        else  

        {  

            MAXSIZE *= 2;  

            char* tempPool = (char*)realloc(pool,MAXSIZE);  

            if(tempPool == NULL)  

                return 0;  

            pool = tempPool;  

            return 1;  

        }  

    }  

    int BigChunkStack::shrink()  

    {  

        if(MAXSIZE/2 >= INITSIZE)  

        {  

            char* tempPool = (char*)realloc(pool,MAXSIZE/2);  

            if(tempPool == NULL) return 0;  

            pool = tempPool;  

            MAXSIZE /= 2;  

            return 1;  

        }  

    }  

6、栈空间的最大值是多少

栈空间:自动内存空间,其中数据的大小在编译时确定,数据的分配和释放也由编译器在函数进入和退出时插入指令完成,数据生命周期和函数一样。

堆空间:动态(手动)内存空间,其中数据的大小和初始值在运行时确定,数据生命周期不定。

Linux默认的用户栈空间的大小是8MBwindows下,栈的大小是2MB,而申请堆空间的大小一般小于2GB。对于堆空间来说,默认是没有最大值的。

7、什么是缓冲区溢出

缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢的数

据覆盖在合法数据上,

 危害:

缓冲区溢出中, 最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码, 比如得到 shell,然后为所欲为。通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。 造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数 .

 

 

展开阅读全文

cocos2d自己总结的几个和内存相关问题

08-20

昨天第一次把程序放到真机上运行,结果程序运行2,3秒后,直接当掉。当时就被吓啥了,马上就要交程序了,以前用模拟器运行都是好好的,咋个一上真机就不行了啊?在网上一搜,才发现是内存出了毛病。于是检查了下内存占用情况,不看不知道,一看吓一跳,程序初始加载内存就有360m!,玩了几关后,内存马上就飚到了500m。这可太恐怖了。于是自己花了1整天的时间,仔细研究了一下cocos2d的内存问题。经过一系列优化,把内存占用降到了最高只有190M左右,这才解决了这个让人紧张万分的问题。以下是我整理的几个和内存相关的点,希望对遇到同样内存问题的朋友有所帮助! rn rn1.慎用大图片 rn图片占用内存大小的算法为一张1024*1024的图片,加载成纹理后,占用1024*1024*4(4m)内存。渲染成精灵后,又会占用和纹理一样的内存,所以这种图片占用的总内存是8m。(绘制精灵的时候,以精灵的绘制这种图片的大小计算内存,例如只绘画这张图片的 某个 28*28大小的 区域, 那么只需要一个 32 * 32 *4 = 4K的内存大小) rnrn2.图片尺寸很重要 rn图片尺寸里有大陷阱,1024*1024和1025*1025的图片看似差不多,其实内存占用有天壤之别,因为cocos2d对图片的尺寸是按2的N次方自适应的,所以1025*1025的图片加载后实际纹理大小为2048*2048,纹理将占用8m内存!是1024*1024那张图片的2倍。所以遇到类似尺寸图片,最好先用PS调整下大小。 rn rn3.使用工具查看内存占用 rn用xcode自带的Leaks工具可以很方便的查看程序占用的情况和寻找内存泄露的堆栈。 rn rn4.检查对象有没被释放 rn当代码越来越复杂的时候,你就要担心某些对象可能在其他你没注意的地方给保留了引用计数,导致你在对这个对象使用release方法时,实际对象并没有被dealloc掉。所以你得通过在每个对象的dealloc方法中打上断点调试或者输出日志,来确认该对象是否真的被释放了。 rn rn5.使用真机测试 rn一般在模拟器中,因为电脑内存本身较大,所以在这种情况下,即使你的程序占用内存很高,也不会收到任何警告和异常。但是如果在iphone或者ipad上进行真机测试,因为设备可用内存较少,所以大多数的内存问题就会暴露出来。所以提早进行真机测试是很有必要的。 rn(在真机上,程序运行效率也会大大提升,在模拟器上游戏帧数10帧不到,放到真机上居然是60帧满的,mac mini的性能真是菜啊!) rn rn6.内存警告。 rn当机器内存吃紧的时候,会给程序一个内存警告消息。这个消息分为1和2两种等级,等级1的时候基本上问题不大。等级2的时候就要注意了,通常这时离崩溃不远了(程序内存占用在350M左右一般会收到等级2的警告。)。如果程序的内存占用超过400M,基本上这时程序就会直接被操作系统杀死,表现在ipad上就是程序直接退出。而这时候程序不会打印任何有助于你检查的log! rnrnrn惊世科技 张世本 rnQQ:454882367 rnMSN:supershiben@hotmail.com rn2011 / 8 / 20 rn 论坛

没有更多推荐了,返回首页