C# 堆和栈

文章详细介绍了堆和栈的概念,包括托管堆与普通堆的区别,栈的先进后出特性,以及堆的内存分配特点。在.NET环境中,垃圾回收器(GC)负责管理内存,开发者无需手动释放。文章还讨论了值类型和引用类型在内存中的存储方式,特别提到了String对象的特殊处理以及数组作为引用类型的情况。最后,简要提及了栈和堆可能导致的内存问题,如栈溢出和堆中的内存泄漏。
摘要由CSDN通过智能技术生成

一 :堆和栈的详细介绍

1、概念

堆(Heap):在c里面叫堆,在c#里面其实叫托管堆。

栈(Stack):就是堆栈,简称栈。

2、托管堆:

托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。

3、栈

栈区的特性:先进后出 入栈的时候是123按顺序入的,那么出栈的时候就是321按顺序出。

当一个栈结束时会把存在栈中的数据依次出栈。

②内存堆栈:存在内存中的两个存储区(堆区,栈区)。

      栈区:存放函数的参数、局部变量、返回数据等值,由编译器自动释放

      堆区:存放着引用类型的对象,由CLR释放

4、堆

堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型的数据对象。与栈不同,堆里的内存能够以任意顺序存入和移除,除了string 先进先出。虽然程序可以在堆里保存数据,但并不能显式地删除它们。CLR的自动GC(Garbage Collector,垃圾收集器)在判断出程序的代码将不会再访问某数据项时,自动清除无主的堆对象。

5、内存分区

当我们编写程序的时候,操作系统把我们编写的程序分五个区 分配在内存中。

这五个区是:栈区、堆区、常量区、静态区、代码区 ,static的变量放在静态区

  1. 栈区:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针),静态区对象的引用地址(指针),常量区对象的引用地址(指针)等

  1. 堆区(托管堆):用于存放引用类型对象本身。在c#中由.net平台的垃圾回收机制(GC)管理。栈,堆都属于动态存储区,可以实现动态分配。

  1. (重点看)静态区及常量区:用于存放静态类,静态成员(静态变量,静态方法),常量的对象本身。由于存在栈内的引用地址都在程序运行开始最先入栈,因此静态区和常量区内的对象的生命周期会持续到程序运行结束时,届时静态区内和常量区内对象才会被释放和回收(编译器自动释放)。所以应限制使用静态类,静态成员(静态变量,静态方法),常量,否则程序负荷高。

  1. 代码区:存放函数体内的二进制代码。

6、GC:

当我用new创建一个对象时,当可分配的内存不足GC就会去回收未使用的对象,但是GC的操作是非常复杂的,会占用很多CPU时间,对于移动设备来说频繁的垃圾回收会严重影响性能。

使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切。

二:值类型和引用类型

数据项的类型定义了存储数据需要的内存大小及组成该类型的数据成员。类型还决定了对象在内存中的存储位置——栈或堆。

类型被分为两种:值类型和引用类型,这两种类型的对象在内存中的存储方式不同。

  • 值类型只需要一段单独的内存,用于存储实际的数据。在创建一个值类型的时候,只是在栈中开辟一个空间(所谓的栈是指暂时存放值的地方就像是一个客栈,比较方便人们的进出,所以值类型的效率比引用类型的效率要高)

  • 引用类型需要两段内存:第一段存储实际的数据,它总是位于堆中;第二段是一个引用,指向数据在堆中的存放位置,存储在栈中,就是在栈中存放一个引用来指向堆中一个值。

  1. 引用类型

假设有一个引用类型的实例,名称为student,它有两个成员:一个值类型成员和一个引用类型成员。它将如何存储呢?是否是值类型的成员存储在栈里,而引用类型的成员如上图所示的那样在栈和堆之间分成两半呢?答案是否定的。

请记住,对于一个引用类型,其实例的数据部分全部存放在堆里。既然两个成员都是对象数据的一部分,那么它们都会被存放在堆里,无论它们是值类型还是引用类型。

对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型。

  1. String

String str1 = "123";
String str2 = str1;
str2 = "321";
Console.WriteLine(str1);

上面代码的最终输出结果是123,如果有浅学过引用类型的同学一定会问:str2不是在存储的是str1的引用么?那么str2不是和str1指向堆中同一块内存空间么?为什么在引用了str2使其改变数据后再打印出str1最终还是打印出来123?

在c#中,String的存储方式很特殊,在c#的内存中,在常量区里会分配一块空间叫做String暂存池(常量池),在某些时候,我们的字符串数据是存储在这个常量池中的,而地址依然是存放在栈中。

例如用 String str = "xXXXX" 的方式来创建String变量的话,那么String的值便会存储在String常量池中,在我们以这种方式创建String变量时,编译器会先判断你这个内容有没有已经在常量池出现过了,如果已经出现过,那么不会再在常量池中使用空间来存放一个相同的内容,这个内容只会固定有一个引用,所以在创造相同内容的String的时候,他们的引用都是相同的。又有一种情况:一开始A和B内容相同,就是说A与B的引用都相同时,此时将B的内容更改,那么B的内容在常量池中就会使用另一块空间,那么相应的B的引用也会改变,而A的引用并不会改变,因为A此时还是存储的原来的内容。我们可以来看简易的图解:

  1. 数组

数组是引用类型,与所有引用类型一样,有数据的引用以及数据对象本身。引用在栈或堆上,而数组对象本身总是在堆上。

尽管数组总是引用类型,但是数组的元素可以是值类型也可以是引用类型。

  • 如果存储的元素都是值类型,数组被称作值类型数组。

  • 如果存储在数组中的元素都是引用类型对象,数组被称作引用类型数组。

  1. 内存泄漏

:空间较小(一般1-2M),比较快,但是可能会爆

1.算法没写好,函数调用太多了;比如死循环,无限循环的递归调用,会报StackOverFlow异常

2.写的程序有错误,不小心在栈上分配了太多内存,即栈内存溢出问题);比如

很大(数G),不会爆,未回收可能会造成内存泄漏(C#中有垃圾收集器,不需要手动释放)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值