内存申请风波

来源:互联网 发布:yy官方协议软件 编辑:程序博客网 时间:2024/05/02 02:31
       想起来身体都发热。那天,面试官问我:“32位的windows程序一次最大能申请多大的内存?”坑爹呐!我平时再怎么无聊也不会无缘无故去测试系统一次性最大能申请多大内存啊!我只能为难地笑着说:“这个我还真没试过······”面试官很好心,给我解释道:“32位系统的内存地址有32位,寻址能力有4GB,所以能申请到的内存最大不超过4GB。”
       我愣了一会,好像忽然明白了什么,这类似的概念不是在汇编语言里面学到过吗?我心里很肯定地对自己说“是的”。但是,说到位数,我最先联想到的是寻址能力,2的32次方等于4GB我也想到了,但是我就是不敢轻易地说出4GB,因为潜意识不停地在提问着我:有这么大的寻址能力就一定能申请到这么多内存空间吗?没这么简单吧?系统应该对此有很多限制的吧?但具体有哪些限制我也不知道,没具体做过试验不敢随便说。
       等我回过神来已经过了数秒,我怕我迟疑得太久会给面试官留下不好的印象(自我感觉到最后也没留下什么好印象),所以我勉强地点了下头,继续后面的面试······不要在这个时候问我面试结果,我也不知道,反正我是不敢抱什么希望的。
       今天,我带着这个疑问,稍微做了下测试。虽然,我的机子上装的是64位的Windows 7 ,我机子的内存也有8GB,与32位Windows 7 和4GB内存的模拟环境的差别可能有点大,但是我打算在写好测试代码后扔到虚拟机里面的32位XP里面去测试的,况且我也好想在现在的系统里看看任务管理器中的内存曲线一下子升高的壮观景象。遂敲测试代码如下:

#include<iostream>#include<iomanip>int main(){    size_t iSize = 1024 * 1024* 1024 * 4 - 1;    //4GB,减1是为了防止32位无符号数溢出,没能到达最大值    char* p = NULL;    p = new char[iSize];    //申请内存空间    std::cout<<std::setiosflags(std::ios::showbase);    //这句是为了输出十六进制地址时前面带有0x    if(p)    {        std::cout<<"成功申请"<<iSize<<"字节的内存!"<<std::endl;        std::cout<<"获得的内存起址为:"<<std::hex<<(int)p<<std::dec<<std::endl;    }    system("pause");    //要手动停住程序,不然一下子闪过了什么也看不到,内存也释放了,任务管理器里也看不到    if(p)    {        delete[] p;        p = NULL;        std::cout<<"成功释放"<<iSize<<"字节的内存!"<<std::endl;    }    return 0;}

       程序目的是:在申请内存成功后输出申请到的大小以及起始地址,并且在释放时有提示;如果内存申请不成功的话就是啥输出都没有。我期待着打开任务管理器,然后让IDE编译、连接、运行·············想不到程序申请内存没有成功,但是输出也不是静悄悄的,而是狠狠地给我抛出了一个“bad_alloc”异常。经过Google之后,得知“bad_alloc”就是new分配内存失败后抛出的异常。哥又震惊又高兴,因为在以往的撸码经历中,“内存分配失败”一直是一个传说级的话题,今天终于让我遇到了,呵呵。



       高兴之余又感到奇怪。我8GB内存耶,我轻轻地向系统要了4GB而已(还差1B才够呢呢- -),你就给我抛异常,什么态度啊?!这在32位系统4GB内存里还好说。我心里现在已经确认“32位系统最大能申请4GB内存”这个说法是有问题的了,但是我想继续看看会发生什么事。
       我尝试着将iSize改成3GB,但是结果还是抛“bad_alloc”异常,改成2GB也一样,我越来越感到疑惑了。但当我将iSize改成1GB的时候,程序正常运行了!



       真是奇怪啊!我8GB内存,系统居然才给我最大申请1GB,这不是明摆着的坑么?这时候,我想起了操作系统课中学过的知识:内存中不一定时刻存在着连续的大块空间,由于各种程序的不定时申请和释放内存,可能会造成大量的内存碎片。可是,还是那句话,我的是8GB内存啊,任务管理器中原先明明只是显示已使用2G多,windows不会无耻到将2G多的已使用内存分散到8GB内存中的各个角落吧?这个理由太牵强了!
       说到任务管理器,我才注意到即使成功申请了1GB的内存,但任务管理器中的内存使用曲线居然没有波动············这时候我立刻联想到会不会是要往内存里边写数据的时候才能看到任务管理器有变化呢。于是我将测试代码改成如下:

#include<iostream>#include<iomanip>int main(){    size_t iSize = 1024 * 1024* 1024 * 1.0f - 1;    char* p = NULL;    p = new char[iSize];    std::cout<<std::setiosflags(std::ios::showbase);    if(p)    {        std::cout<<"成功申请"<<iSize<<"字节的内存!"<<std::endl;        std::cout<<"获得的内存起址为:"<<std::hex<<(int)p<<std::dec<<std::endl;//-----------------------------往已申请的内存里写数据------------------------------        for(size_t i = 0; i < iSize; ++i)        {            p[i] = i % 256;        }//-------------------------------------------------------------------------------------    }    system("pause");    if(p)    {        delete[] p;        p = NULL;        std::cout<<"成功释放"<<iSize<<"字节的内存!"<<std::endl;    }    return 0;}

       这次申请的1GB内存在任务管理器中终于体现出波动来了。由此可得出结论:在堆上开辟了内存空间以后,要往里面写数据才能看到任务管理器的内存占用曲线有变化。但是为什么这样呢?为什么不在一申请到内存时就显示内存的占用变化情况,而非要等到写数据进去时才发生变化呢?我谷歌百度好多资料都找不到答案,希望有知道的热心朋友能回答。



       呃···········上面解决的问题好像帮不了“最大能申请多大内存”这个问题。无奈之下我只好直接谷歌“32位windows系统最大能申请多大内存”,找到的资料还真不少。原来,正确的说法是:
       在windows 32位操作系统中,每一个进程能使用到的最大空间(包含操作系统使用的内核模式地址空间)为4GB , 在通常情况下操作系统会分配2GB内存给进程使用,另外2GB内存为操作系统保留。
       “所有 32 位应用程序都有 4 GB 的进程地址空间(32 位地址最多可以映射 4 GB 的内存)。对于 Microsoft Windows 操作系统,应用程序可以访问 2 GB 的进程地址空间,称为用户模式虚拟地址空间。应用程序拥有的所有线程都共享同一个用户模式虚拟地址空间。其余 2 GB 为操作系统保留(也称为内核模式地址空间)。
       原来如此,操作系统为了满足自身需要,占用了2GB的地址空间,所以4GB的寻址能力就剩下了2GB给我们的用户进程了。这里并不是说,用户的进程就只能访问2GB的内存,这里要注意的是地址空间。地址空间与物理内存空间不同,就我个人的理解,地址空间是指32位地址能表示的最小最大的地址范围,而物理内存空间指的是实际上拥有的物理内存的大小。地址空间能表示的大小不代表物理内存就有这么大,两者间的关系完全可以是小于、等于或大于。若地址空间小于物理内存空间,那么即使你有再大的物理内存,你也无法使用,因为地址空间无法映射到多出来的那部分内存,即无法定位到那里,这就是为什么在8GB内存机子上安装32位系统时,能识别的只有3G多的内存,我想实际上是识别了4GB的,因为一部分的地址空间被系统占用了,留下的3G多就给用户进程使用。
       我尝试修改我的测试代码中iSize的大小,但发现在我的机子上一次能申请的内存至多只能去到1.81GB,而无法真正地到达网上所说的2GB,我想这里所说的2GB应该是个约数吧,不少人反映他们的机子最多只能申请1.5~1.6GB的内存,所以我认为每个版本系统的系统态和用户态所占用的地址空间是不同的,但都某个范围内上下波动,而这个范围是可以通过修改boot.ini或者使用bcdedit来修改的。详细做法可以参考文章后的网址。
       不过回过头来想想,我的系统是64位的啊,而且内存也是8GB的,理论上一次性能申请的最大内存应该稍微比2GB大一点吧?为何结果还是跟32位系统的相差甚少?经谷歌后又一发现:程序也必须是64位的才能申请超过2GB的内存,而编译出来的程序究竟是多少位的是由编译器来决定的。我用的是CodeBlock的windows版本,其中自带的默认编译器只能编译出32位的exe,所以即使我的系统是64位的,且内存也有8GB,但是由于程序是32位的,所以程序运行结果也是32位的。
       
       有趣的是,我当时查看内存占用情况时,不止打开了任务管理器,还打开了任务管理器中的资源监视器。在资源监视器中的内存选项卡一栏中,可以清楚形象地看到内存的具体占用情况:灰色的是为硬件保留的,其中存储着一些跟BIOS和其他外设的驱动程序相关的数据;绿色的是正在使用的,使用者包括进程、驱动程序和操作系统;橙色的是已经被修改过的,即数据被加载进内存后已被修改过的,在页面对换时,这部分内存中的数据在被覆盖之前必须被写回硬盘上;蓝色的是备用的,这里的备用包括未被使用的非活跃数据或代码,这部分数据或代码是随着某些将要被使用的数据或代码一起加载进内存的,由于它们的位置在逻辑上靠得比较近,根据程序的局部性原理,这部分数据很可能很快也会被访问到,与其到时再启动磁盘慢吞吞地将其加载进来,不如一次性将附近的大块数据或代码加载进内存以减少磁盘访问次数和提高访问速度。浅蓝色部分是可用空间,我们申请内存空间时,系统首先会到这部分来寻找空间。



       比较巧合的是,我当时测试出我的系统最大能允许我的程序一次性申请1.81GB内存空间时,内存中浅蓝色部分的可用空间也是1.8GB左右,所以我就误以为是该部分的大小限制了我的程序能一次性申请的最大内存空间。于是,我就好奇了,当这部分可用空间被压榨完毕时,再申请额外的内存空间会发生什么事呢?上文说到过,用户进程所访问的内存空间不止是2GB,为了与“用户进程只能使用2GB内存”区别开来,现在顺便验证一下。所以我就同时打开了测试程序的多个实例,这次我打开了5个,理论上应该占用9.05GB内存。



       可以看到,绿色的正在使用内存不断扩张,浅蓝色的可用空间慢慢减少,直到消失,这时候可用空间显然不够用了,那怎么办呢?此时,操作系统居然拿出了老本,将蓝色的备用内存逐渐转化成绿色的正在使用内存,哈哈,真会做啊。在这一时间段内,我发现电脑上的硬盘灯在闪个不停,我知道,传说中的页面对换已经开始了。刚才说到,蓝色部分的备用内存是缓存以后可能要用到的数据和代码,但是到了内存紧缺时,这部分备用的内存将会成为页面对换的首选,因为里面的内容还不是活跃的,操作系统在思考着人生:与其憧憬未来,不如踏实做好现在。于是,爽快地将备用的缓存内容放弃掉,转化为我的程序的占用空间了。硬盘灯是因为将备用内存转化为正在使用内存而闪烁吗?其实橙色的内存也是可以置换出去的,虽然他与备用内存的颜色不同,但是他是已被修改过的但暂未被再次访问的内容,也属于备用的,但是与蓝色备用内存不同的是,橙色的必须先写回硬盘再被重分配给其他应用程序,而硬盘灯的闪烁就是在做着将数据写回硬盘的工作,对于我这个测试例子来说起码是这样。要将数据写回硬盘,就意味着要启动磁盘IO,而磁盘IO的速度是远慢于内存访问速度的,所以在截图的这段时间内,我的机子硬生生地假死了几十秒··················= =!



       测试完毕,关掉所有测试实例后,发现这真是一个清理内存的好办法涅,可以看到绿色部分的正在使用内存在慢慢缩小,而浅蓝色部分的可使用空间在逐渐增大甚至超过了原来的大小,而橙色已修改部分和蓝色备用部分的内存没有太大变动。与此同时,硬盘灯也在繁忙地闪烁,这是因为系统在将刚才内存紧张时挂起的进程重新加载到内存来激活,以及将其附近的数据和代码也一并加载近来成为备用内存部分,虽然表面上看到的蓝色备用内存变化不大,但仔细看会发现,蓝色的备用内存正在以每秒几MB的速度恢复,真是像RPG游戏中的角色正在恢复魔法啊。若此时进行刷新或者打开其他应用程序,你会发现很卡,就像刚刚玩完大型游戏似的,这是因为要访问的数据不在内存中,而引发了内存缺页中断,这时要等操作系统启动磁盘IO将所需数据读入到内存才能继续访问,所以就卡了
      说到游戏,我就想到了当时高二玩《天下贰》的时候的情景跟现在几乎一样。当时我的机子只有2GB内存,在打开《天下贰》之前已经被占用了1G多,还真担心跑不动呢。但是进入游戏之后,也没有发现很卡,跟平常时一样。但当退出《天下贰》时,发现内存占用降到了几百MB,而且刷新或者打开其他应用程序时会很卡。在这里,《天下贰》就相当于上面的测试程序,真是相似啊,好怀念当时玩的《天下贰》啊·············

       扯到现在,发现我原本只是为了测试32位系统能一次性申请多大空间而已···············现在想起面试官问我这个问题,发现我真的糗爆了。先不说我当时点头是否真的明白,我发现他的解答根本就是在骗我··········或许他是故意的呢?也许他想试试我能否发现错误呢。结果我还是乖乖地被他忽悠了,哈哈哈。
       也罢,经一事长一智,哥我就是不知不觉,后知后觉类的人啊。这段时间面试遗留下来的问题,可真是一笔财富哟,只要我能一个一个地将它们击破···········

参考文章:
http://blog.csdn.net/yusiguyuan/article/details/12405799
http://blog.chinaunix.net/uid-26611383-id-3802969.html
http://www.felix021.com/blog/index.php?go=category_13

0 0
原创粉丝点击