不用程序员操心的堆 —托管堆
程序在计算机上跑着,就难免会占用内存资源来存储在程序运行过程中的数据,我们按照内存资源的存取方式将内存划分为堆内存和栈内存。
栈内存,通常使用的场景是:对存取速度要求较高且数据量不大。
典型的栈内存使用的例子就是函数栈,每一个函数被调用时都会被分配一块内存,这块内存被称为栈内存,以先进后出的方式存取数据,在函数执行过程中不断往函数栈中压入(PUSH)数据(值类型数据:int、float、对象的引用...),函数执行完后又将函数栈中的数据逐个弹出(POP),由于是以操作栈的形式来存取,所以访问速度快。
堆内存,从字面意思上理解就好像是仓库里面可以存一堆破烂,你若是需要存点什么东西就尽管往里面一扔,仓库里有的是空间。事实确实也是如此,堆内存中可以存放大规格的数据(比如对象资源),这些数据是不适合存放在栈中的,因为栈空间的容量有限,这就是堆内存相对于栈内存的好处:容量大。但是它的缺点也是显而易见的,那就是存取堆内存的数据相较于存取栈内存是非常慢的,试想一下,让你在仓库里的一堆破烂里去找你想要的东西是什么感觉。
从内存分配方式上看,堆内存不同于栈内存,函数栈是在每一个函数被执行的时候被自动分配并且函数执行完成后自动回收,而如果你想使用堆内存,就得自己动手丰衣足食。
所以你会看到c语言程序员会这样去使用堆内存:
int *p = (int*)malloc(sizeof(int)); //在堆内存中申请一块字节数为int字节数的堆内存,并返回指向该内存区域的指针 *p = 10; free(p); //释放堆内存资源
你还会看见c++程序员这样写:
Car* bmw = new Car(); //创建一个Car类对象,在堆内存中存放对象数据,并返回指向对象资源的指针 delete bmw; //释放堆内存资源
当然,没有接触过c/c++的小伙伴也不用惊慌,上面只不过是想让你知道在c/c++语言中,程序员要是想使用堆内存,那就必须显式地编写分配和释放堆内存资源的代码。
有人问:使用完堆内存资源后没有手动释放它会有什么后果吗?
答案是:由于堆内存资源使用者未及时释放内存会导致内存无法再次使用,从而造成内存资源的泄漏(浪费)。
就在这个时候,c#程序员笑了,只见他的手指非常轻盈优雅地在屏幕上敲出了下面这行代码:
Car bmw = new Car();
一旁围观的c程序员和c++程序员惊呆了,他们不知道自己在敲代码的时候有没有像这样轻松过。c++程序员用手抚摸着他那锃光瓦亮的额头,突然眼睛里闪着光,喊道:“你还没有释放堆内存的资源呢,你这样是很危险的,会内存泄漏的,快,把释放堆内存的代码写上!”
c#程序员似乎并不为所动,舒舒服服地靠在椅子上,用余光瞟了c++程序员一眼,说:“不用慌,不用慌,这个对象在托管堆上放的好好的呢,不用我操心”,于是,c#程序员便娓娓道来(呼呼大睡)...
在.NET的世界,使用new关键字创建一个对象,首先对象资源被分配在托管堆中,然后new会返回一个指向堆上对象的引用,而不是真正的对象本身。如果在方法作用域中将引用变量声明为本地变量,这个引用变量保存在栈内,以供应用程序以后使用。
托管堆,顾名思义,就是托给别人管的堆,那么是谁在管理着这个堆上的对象资源呢?
答案是:CLR(Common Lanauage Runtime),对象的实例化结束以后,GC(垃圾回收器)将会在对象不再需要时将其销毁。
也就是说,通过允许垃圾收集器负责销毁对象,内存管理的麻烦就都交给CLR了,万事大吉。
看似问题好像都已水落石出,无非就是将堆内存资源回收交给了CLR去承担。难道你就不想知道的更多一点?比如接着而来的问题:
1、垃圾回收器如何判断一个对象什么时候不再需要?
2、垃圾回收器又在什么时候会执行垃圾清理的操作?
别急,带着问题慢慢往下看。
CIL的new指令— 垃圾回收的触发者
c#中的new关键字最终会被编译器翻译成CIL的newobj指令,让我们仔细查看一下CIL newobj指令的作用。