C# 内存机制 – 基础篇


原文:https://zhuanlan.zhihu.com/p/113513147

腾讯面前端暑期实习还有一道让我有点窒息的题目,当时好像是问我C#内存分块底层?我有点不大懂面试官的意思。所以干脆一起总结一下免得无话可说。

1 Overview

首先开宗明义,C#是一种托管语言,它的垃圾回收机制(GC)是由.net平台负责的,加之C#语言并没有指针,所以我们在使用过程中极少会考虑到内存使用状况以及项目在运行过程中是如何进行内存管理的。但是,C#只是在内存管理方面对程序员隐藏了,并不代表它不涉及这些东西,甚至其内部内存管理或许比自己手动管理更加复杂。(我似乎明白为啥我对C#内存管理一无所知了

这里复习一下托管与非托管代码:

托管代码CLR由运行环境执行的代码(非操作系统)可以自动获得语言运行库服务,自动垃圾回收,类型检查,安全等。

  • 不能直接访问内存,所以不使用指针(因为没有直接操作底层API,而是通过环境)(需要创建类库即DLL)

而非托管代码则与公共语言运行库无关,生成机器可以直接执行的二进制代码。

  • 可以使用指针直接读取内存

所以C#作为托管语言,是由运行环境负责执行的,自动垃圾回收类型检查安全等等。但是就像C++一样,C#内存也是分区域的,值类型,引用类型,指针,指令等等。

  • 值类型:常见的primitive type,比如int char
  • 引用类型:继承自System.Object,也就是对象,也包括String
  • 指针类型:在内存区中,指向一个类型的引用,通常被称为“指针”,它是受CLR( Common Language Runtime:公共语言运行时)管理,我们不能显式使用。指针在内存中(栈区)占一块内存区,它本身只代表一个内存地址(或者null),它所指向的另一块内存区(堆区)才是我们真正的数据或者类型。
  • 指令:这个就不说了,PC就是干这个的应该

2 内存分区

1) 栈区:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针),静态区对象的引用地址(指针),常量区对象的引用地址(指针)等。其操作方式类似于数据结构中的栈。

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

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

4) 代码区:存放函数体内的二进制代码。

3 内存分配

值类型肯定直接在栈上分配空间就行,没啥说的

引用类型(对象)在堆区分配空间,但是引用地址(指针)还是在栈区,通过引用地址去堆区内寻找对象本身的值。所以只有引用类型对象会涉及到深拷贝的问题,而值类型都是深拷贝,因为不存在指针,直接栈上就分配了。如果需要深拷贝或者浅拷贝对象请参考我的另一篇文章

json和Jason:C#的深拷贝浅拷贝2 赞同 · 5 评论文章

值得注意的是,静态变量应该是在你或者系统代码第一次访问这个类的时候,clr 会去加载这个类,当然也有其他说法比如说分情况:

  • 编译时已知数据,静态成员直接写入PE文件
  • 加载时可以决定的数据,程序加载时初始化。
  • 运行时才能决定的数据,调用type initialize

总的来说,静态变量生命周期大致是从程序加载到程序销毁,所以变量的指针存放在栈区,值本身存放在静态存储区。

4 内存管理

1) 栈区管理:

C#中栈是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义;栈区内存无需我们管理,也不受GC管理,栈顶元素使用完毕弹出就会立即释放。

2)堆区管理:

堆区是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定要分配的堆内存的大小。在C#中堆区内存由GC(Garbage collection:垃圾收集器)负责清理,当对象超出作用域范围或者对象失去指向的引用地址,就会在一定时间内进行统一的处理,无需程序员手动处理。

当对象不再使用时,这个被存储在堆栈中的引用变量将被删除,但是从上述机制可以看出,在托管堆中这个引用指向的对象仍然存在,其空间何时被释放取决垃圾收集器而不是引用变量失去作用域时。

在使用电脑的过程中大家可能都有过这种经验:电脑用久了以后程序运行会变得越来越慢,其中一个重要原因就是系统中存在大量内存碎片,就是因为程序反复在堆栈中创建和释入变量,久而久之可用变量在内存中将不再是连续的内存空间,为了寻址这些变量也会增加系统开销。在.net中这种情形将得到很大改善,这是因为有了垃圾收集器的工作,垃圾收集器将会压缩托管堆的内存空间,保证可用变量在一个连续的内存空间内,同时将堆栈中引用变量中的地址改为新的地址,这将会带来额外的系统开销,但是,其带来的好处将会抵消这种影响,而另外一个好处是,程序员将不再花上大量的心思在内存泄露问题上。

垃圾收集器除了会压缩托管堆、更新引用地址、还会维护托管堆的信息列表。

以后我在整理一下页面置换的一些问题(复习OS

参考资料:

CSDN-专业IT技术社区-登录

原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/276346.html

(0)
上一篇 2022年7月23日
下一篇 2022年7月23日

相关推荐

发表回复

登录后才能评论