内存四区的理解


蚕丛及鱼凫,开国何茫然。

前言

内存四区,之前学C语言的时候,就模模糊糊的看了一下,也没有很仔细的琢磨,后来学PHP和Java在看一些视频的时候,就看到了那些培训的老师,都对这些讲述的很细。有必要系统的总结一下啦,不然底层都弄不好,还怎么学习啊。
这里就懒得去找其他语言的例子来讲了,就用C语言来讲一下吧。

入门案例

详解内存四区

图解内存四区

内存四区介绍

栈区

由编译器自动分配释放, 存放函数的参数值,局部变量等.

例如: 参数buf,参数bufsize和size都是存放在栈区.当函数执行完毕的时候,自动释放

void  recev(char* buf, int bufsize){
            int size;
}

堆区

一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收

例如:下面的src所指向的内存空间就是在堆区

char* src = (char*) malloc(sizeof(buf) * sizeof(10));

全局区(静态区、常量区)

全局变量和静态变量存放在此. 里面细分有一个常量区, 字符串常量和其他常量也存放在此. 该区域是在程序结束后由操作系统释放.

程序代码区

这个区域存放函数体的二进制代码.也是由操作系统进行管理的

划分内存四区的意义

C语言程序中,根据是局部变量,全局变量, 常量还是通过malloc等类似的函数分配内存空间, 把他们放到对应的内存区中.这样就赋予了这些变量或常量不同的生命周期, 不同的释放方式. 根据我们程序的需要,我们在编码过程中,声明不同的变量类型, 使他们有不同的声明长度, 不同的释放方式,给我们更大的灵活编程

理解时出现的疑问

关于栈内存的进栈顺序

正如入门案例图上所示,都是操作系统或者某函数的状态在最下面,接下来是返回地址。程序读取的时候,按道理难道不应该是从main函数依次往下读取,最后才能读取到返回值么?

标准解释

执行双击的时候,是操作系统将软件加载到内存。操作系统的可执行程序也是提前加载到内存的。
操作系统找到main函数入口,开始执行。操作系统将应用程序分成四个区:代码区,堆区,栈区和全局区。
在网上找到这个图

更加通俗的说法:
说法一:

图上main函数橙色的区域:这4块内容,在main函数执行结束之后,是先后一起弹出来的。局部变量和形参弹出来之后就丢弃了,可以视为扔掉了。返回地址和其他现场信息(运行状态信息)弹出来之后会被送到CPU或者其他相关芯片或者某些变量中,以便恢复到该程序被调用之前的状态。

说法二:

相当于现场信息是对刚开始的操作系统的运行状态拍快照,程序结束后,从main函数的栈区里面把这个快照取出来,然后去恢复环境,把环境恢复为原来调用之前操作系统的各方面的样子。

说法三:

打一个比方:比如你现在正在数钱,数到第320张的时候,用户来找你,让你去执行他的main函数。你怎么办?

你只有一个办法,放下手里的活,去执行。但是,执行完了之后,等你回来,难道你要重新开始数钱么?当然不会。这个时候,最聪明的做法,就是,在你转去执行main之前,先记录一下你现在数了多少张,并且卡一个书签在数过及没数过的钱之间。

这个书签,就是返回地址。你回来之后,从这个位置开始继续往下数钱。这个记录下来的已数过钱的数额,就是所谓的现场信息。

在操作系统中,没有书签这个东西。他们会记录下一些当时cpu跳转之前,周围一些必要的数据信息,以便于回来之后继续执行

而这些信息,它是带到了main的栈区里面去保存的。

在执行main函数时,它会放心大胆滴修改cpu各个寄存器的值。各条总线的值,各个开关的值。

等执行完程序,回来之后,它会按照栈区里面弹出来的这个现场信息的内容,去恢复现在的现场,恢复成调用之前的样子,然后继续作后面的事。

关于内存泄漏

内存泄漏的原理

内存泄露的原理,当我们再堆区中分配的内存如果没有手工释放,程序结束后会把栈中的内存回收释放,该栈中保存了一个指向堆区元素的指针。然而这个指针一旦释放了后,就再也没有元素指向堆区的那块元素,然而堆区中的那块元素又没有释放,这自然而然的造成了内存泄露。

垃圾对象回收几种语言的比较

C和C++没有垃圾回收机制,所以一旦malloc之后的空间不再使用,成为垃圾之后,需要程序员自己去调用free函数来释放空间。

PHP 销毁资源使用析构函数。析构函数是在对象被销毁时自动释放的,不需要调用。

JAVA (因为自己还没有学到JAVA的垃圾回收,只能通过网上的这些资料,等学到了,再用自己的语言总结一下) Java中的垃圾回收与对象生命周期

堆内存在JVM启动的时候被创建,堆内存中所存储的对象可以被JVM自动回收,不能通过其他外部手段回收,也就是说开发人员无法通过添加相关代码的手段来回收堆内存中的对象。堆内存通常情况下被分为两个区域:新对象区域与老对象区域。

新对象区域:又可细分为三个小区域:伊甸园区域、From区域与To区域。伊甸园区域用来保存新创建的对象,它就像一个堆栈,新的对象被创建,就像指向该栈的指针在增长一样,当伊甸园区域中的对象满了之后,JVM系统将要做到可达性测试,主要任务是检测有哪些对象由根集合出发是不可达的,这些对象就可以被JVM回收,并且将所有的活动对象从伊甸园区域拷贝到To区域,此时一些对象将发生状态交换,有的对象就从To区域被转移到From区域,此时From区域就有了对象。上面对象迁移的整个过程,都是由JVM控制完成的。

老对象区域:在老对象区域中的对象仍然会有一个较长的生命周期,大多数的JVM系统垃圾对象,都是源于”短命”对象,经过一段时间后,被转入老对象区域的对象,就变成了垃圾对象。此时,它们都被打上相应的标记,JVM系统将会自动回收这些垃圾对象,建议不要频繁地强制系统作垃圾回收,这是因为JVM会利用有限的系统资源,优先完成垃圾回收工作,导致应用无法快速地响应来自用户端的请求,这样会影响系统的整体性能。