不用程序员操心的堆 — 托管堆        

  程序在计算机上跑着,就难免会占用内存资源来存储在程序运行过程中的数据,我们按照内存资源的存取方式将内存划分为堆内存栈内存

    栈内存,通常使用的场景是:对存取速度要求较高且数据量不大

    典型的栈内存使用的例子就是函数栈,每一个函数被调用时都会被分配一块内存,这块内存被称为栈内存,以先进后出的方式存取数据,在函数执行过程中不断往函数栈中压入(PUSH)数据(值类型数据:int、float、对象的引用...),函数执行完后又将函数栈中的数据逐个弹出(POP),由于是以操作栈的形式来存取,所以访问速度快。

  堆内存,从字面意思上理解就好像是仓库里面可以存一堆破烂,你若是需要存点什么东西就尽管往里面一扔,仓库里有的是空间。事实确实也是如此,堆内存中可以存放大规格的数据(比如对象资源),这些数据是不适合存放在栈中的,因为栈空间的容量有限,这就是堆内存相对于栈内存的好处:容量大。但是它的缺点也是显而易见的,那就是存取堆内存的数据相较于存取栈内存是非常慢的,试想一下,让你在仓库里的一堆破烂里去找你想要的东西是什么感觉。

  (栈内存比堆内存详细参考:https://blog.csdn.net/boyxiaolong/article/details/8543676

  从内存分配方式上看,堆内存不同于栈内存,函数栈是在每一个函数被执行的时候被自动分配并且函数执行完成后自动回收,而如果你想使用堆内存,就得自己动手丰衣足食。

    所以你会看到c语言程序员会这样去使用堆内存:

int *p = (int*)malloc(sizeof(int)); //在堆内存中申请一块字节数为int字节数的堆内存,并返回指向该内存区域的指针
*p = ;
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指令的作用。

  首先,需要明白托管堆不仅仅是一个可由CLR访问的随机内存块。.NET垃圾回收器是堆的“清洁工”,出于优化的目的它会压缩空闲的内存块(当需要时)。为了辅助压缩,托管堆会维护一个指针(通常被叫做下一个对象指针或者是新对象指针),这个指针用来标识下一个对象在堆中分配的地址。

  此外,newobj指令通知CLR来执行下列的核心任务:

  (1)计算要分配的对象所需的全部内存(包括这个类型的数据成员和类型的基类所需的内存)。

  (2)检查托管堆来确保有足够的空间来放置所申请的对象。如果有足够的空间,会调用这个类型的构造函数,构造函数会返回一个指向内存中这个新对象的引用,这个新对象的地址刚好就是下一个对象指针上一次所指向的位置。

  (3)最后,在把引用返回给调用者之前,让下一个对象指针指向托管堆中下一个可用的位置。

  下面的图解释了在托管堆上分配对象的细节。

  在c#中分配对象是一个很频繁的操作,照这样下去托管堆上的空间迟早会被挥霍完,所以,重点来了,如果CLR 发现托管堆没有足够空间分配请求的类型时,它会执行一次垃圾回收来释放内存

  当执行垃圾回收时,垃圾收集器临时挂起当前进程中的所有的活动线程来保证在回收过程中应用程序不会访问到堆。(一个线程是一个正在执行的程序中的执行路径)。一旦垃圾回收完成,挂起的线程又可以继续执行了。还好,.NET 垃圾回收器是高度优化过的,所以用户很少能察觉到应用程序中的短暂中断。

  通过对CIL的new指令作用的解读,我们知道了:如果托管堆没有足够的空间分配一个请求的对象,则会执行一次垃圾回收

(讲到这里c#程序员停了下来,喝了口保温杯里的枸杞红枣大补茶

谈谈.net对象生命周期的更多相关文章

  1. Hibernate的三种状态及对象生命周期

        理解Hibernate的三种状态,更利于理解Hibernate的运行机制,这些可以让你在开发中对疑点问题的定位产生关键性的帮助. 三种状态 临时状态(Transient):在通过new关键字, ...

  2. .Net组件程序设计之对象生命周期

    .Net组件程序设计之对象生命周期 .NET 垃圾回收 IDisposable() Using语句 .NET 垃圾回收 是CLR管理着垃圾回收器,垃圾回收器监控着托管堆,而我们使用的对象以及系统启动是 ...

  3. Ninject之旅之三:Ninject对象生命周期

    摘要 DI容器的一个责任是管理他创建的对象的生命周期.他应该决定什么时候创建一个给定类型的对象,什么时候使用已经存在的对象.他还需要在对象不需要的时候处理对象.Ninject在不同的情况下管理对象的生 ...

  4. iOS视图控制对象生命周期

    iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...

  5. IOS 视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途

    iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...

  6. 【转】【iOS知识学习】_视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途

    原文网址:http://blog.csdn.net/weasleyqi/article/details/8090373 iOS视图控制对象生命周期-init.viewDidLoad.viewWillA ...

  7. _视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途

    iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用途 ...

  8. ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

    在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行代码就展示了一个基本工作原理.然而,要将这样的解决方案运用到实际生产环境,还有很 ...

  9. Python学习手册之内部方法、操作符重载和对象生命周期

    在上一篇文章中,我们介绍了 Python 的类和继承,现在我们介绍 Python 的内部方法.操作符重载和对象生命周期. 查看上一篇文章请点击:https://www.cnblogs.com/dust ...

随机推荐

  1. [考试反思]1017csp-s模拟测试77(lrd day1) :反抗

    说在前面:强烈谴责AK神Mr_zkt没有丝毫素质RP-- 然而我也想没素质一次,但是我没机会AK一套除了B组题以外的题... 太菜了,没权力.人家AK了人家就是牛逼你没话说 达哥的题必须好好写反思. ...

  2. 我把数表A了

    真kx 之前打一棵沙雕主席树,复杂度和正解一毛一样,结果常数爆炸了! 我一直天真的认为卡卡常这题就能AC,然后卡常卡到死也没A掉,极限数据大概跑了1.1s 今天终于打正解了,离线处理,可以用常数更小的 ...

  3. swoole不断的切换前端链接方法 防止攻击

    php不断的切换前端链接方法 防止攻击 swoole写法 每分钟生成一次url后缀 返回到客户端让他们更新 //定时器要写在WorkerStart这个里面哦$ws->on('WorkerStar ...

  4. Ansible之常用模块(一)

    ansible之所以功能强大,不是ansible本身,是因为它有众多的模块,前文我们介绍了ansible的基础介绍,系列命令的用法以及选项的说明,通过前文的学习我们知道了ansible是基于pytho ...

  5. 【algo&ds】2.线性表

    1.线性表 线性表(英语:Linear List)是由n(n≥0)个数据元素(结点)a[0],a[1],a[2]-,a[n-1]组成的有限序列. 其中: 数据元素的个数n定义为表的长度 = " ...

  6. 平滑启动shell脚本

    # 平滑关闭和启动 Spring Boot 程序#设置端口SERVER_PORT="8090"#当前时间time=`date +%Y-%m-%d`#设置应用名称JAR_NAME=& ...

  7. 公众号第三方平台开发 component_verify_ticket和accessToken的获取

    公众号第三方平台审核通过之后,微信的服务器会定时(10分钟一次)给"授权事件接收URL"发送component_verify_ticket,这里我们需要及时更新component_ ...

  8. Arduino 处理JSON格式的数据

    Arduino 处理JSON格式的数据 1.安装 ArduinoJson这个包 2.程序代码 # include <ArduinoJson.h> #define ALINK_BODY_FO ...

  9. pat 1006 Sign In and Sign Out(25 分)

    1006 Sign In and Sign Out(25 分) At the beginning of every day, the first person who signs in the com ...

  10. nyoj 833-取石子(七) (摆成一圈,取相邻)

    833-取石子(七) 内存限制:64MB 时间限制:1000ms 特判: No 通过数:16 提交数:32 难度:1 题目描述: Yougth和Hrdv玩一个游戏,拿出n个石子摆成一圈,Yougth和 ...