框架-内存管理-变量创建与销毁

更新时间:2023-07-12 22:40:26 阅读: 评论:0

框架-内存管理-变量创建与销毁
作者:vuefine
⽂献:《C#⾼级编程》 christian Nagel编著
说明:⽂章代码,例⼦,图形,总结思路,都是⾃⼰原创
前⾔
运⾏库通过垃圾回收器⾃动处理回收托管资源,⾮托管的资源需要⼿动编码处理。理解内存管理的⼯作原理,有助于提⾼应⽤程序的速度和性能。废话少说,切⼊正题。主要阐述的概念见下图:
概念
 内存:⼜称为虚拟内存,或虚拟地址空间,windows使⽤虚拟寻址系统,在后台⾃动将可⽤的内存地址映射到硬件内存中的实际地址上,其结果便是32位处理器上的每个进程都可以使⽤4GB的内存,⽤来存放程序的所有部分,包括可执⾏代码(exe⽂件),代码加载的所有DLL,程序运⾏时使⽤的所有变量的内容。
内存栈
 在进程的虚拟内存中,存在的⼀个变量的⽣存期必须嵌套的区域。
内存堆
 在进程的虚拟内存中,在⽅法退出后的很长⼀段时间内数据仍是可⽤的区域。
托管资源
 垃圾回收器在后台能⾃动处理的资源
⾮托管资源
 需要⼿动编码,通过析构函数,Finalize,IDisposable,Using等机制或⽅法处理的资源。
内存栈
观猎王维 值类型数据存储在内存栈中,引⽤类型的实例地址值也放在内存栈中(见内存堆的讨论),内存栈的⼯作原理,透过下⾯⼀段代码理解:
{ //block1开始
语文预习方法int a;
//solve something
{//block2开始
int b;
// solve something el
}//block2结束
}//block1结束
以上代码注意2点:
 1)C#中变量的作⽤域,遵循先声明的后超出作⽤域,后声明的先超出作⽤域,即b先释放,a后释放,释放顺序总是与它们分配内存的顺序相反。
 2)b在⼀个单独的块作⽤域(block2)中,⽽a所在的块名称为block1,其内嵌套着block2。
 请看下⾯⽰意图:
 栈内存管理中,始终都维护着⼀个栈指针,它始终指向站区域中下⼀个可⽤的地址,名字为sp,如图所⽰,假定它指向编号为1000的地址。
 变量a ⾸先⼊栈,假定机⼦是32位的,int型占4个字节,即997~1000,⼊栈后,sp指向996,可见内存栈的增长⽅向为从⾼地址向低地址⽅向。
 然后b⼊栈,占据993~996,sp指向992。当超越块block2 时,变量b⽴即释放在内存栈上的存储,sp增加4个字节,指向996。
 向外⾛,超越块block1 时,变量a ⽴即释放,此时sp再增加4个字节,指向原来的初始地址1000,后⾯再⼊栈时,这些地址再被占⽤,然后再被释放,循环往复。
内存堆
 尽管栈有⾮常⾼的性能,但对于所有的变量它还是不太灵活,因为位于内存栈上的变量的⽣存期必须嵌套。许多情况下,这种要求过于苛刻,因为我们希望有些数据在⽅法退出后的很长⼀段时间内还是可⽤的。
 只要是⽤new运算符来请求的堆存储空间,就满⾜数据声明期延时性,例如所有的引⽤类型。在中使⽤托管堆来管理内存堆上的数据。
 中的托管堆和C++使⽤的堆不同,它在垃圾回收器的控制下⼯作,⽽C++的堆是低级的。
 既然引⽤类型的数据存储在托管堆上,那么它们是如何存储的呢?请看下⾯代码
void Shout()
{
Monkey xingxing; //猴⼦类
xingxing = new Monkey();
}武夷水仙茶
  在这段代码中,假定两个类Monkey和AIMonkey,其中AIMonkey类扩展了Monkey对象。
  在这⾥,我们称Monkey为⼀个对象,称xingxing为它的⼀个实例。
  ⾸先,声明了⼀个Monkey引⽤xingxing,在栈上给这个引⽤分配存储空间,记住这仅是⼀个引⽤,⽽不是实际的Monkey对象。记住这⼀点很重要
  然后看下第2⾏代码:
  它完成的操作:⾸先,它分配堆上的内存,以储存Monkey对象,注意了这是⼀个真正的对象,它不是⼀个占⽤4个字节的地址 假定Monkey对象占⽤64个字节,这64个字节包含了Monkey实例的字段,和中⽤于识别和管理Monkey类实例的⼀些信息。这64个字节实在内存堆上分配的,假定内存堆上的地址1937~2000。new操作符返回⼀个内存地址,假定为997~1000,并赋值给
xingxing。⽰意图如下所⽰:
记住⼀点:
 与内存栈不同的是,堆上的内存是向上分配的,由低地址到⾼地址。
 从上⾯的例⼦中,可以看出建⽴引⽤实例的过程要⽐建⽴值变量的过程更复杂,系统开销更⼤。那么既然开销这么⼤,它到底优势何在呢?引⽤数据类型强⼤到底在哪⾥
   请看下⾯代码:
纹身艺伎图片xingxing = new Monkey();
{//block1
Monkey xingxing; //猴⼦类
xingxing = new  Monkey();
{//block2
Monkey jingjing = xingxing; //jingjing 也引⽤了Monkey 对象
//do something
}
//jinjing 超出作⽤域,它从栈中删除
//现在只有xingxing 还在引⽤Monkey
}
//xingxing 超出作⽤域,它从栈中删除
//现在没有在引⽤Monkey 的了
  把⼀个引⽤实例的值xingxing赋值予另⼀个相同类型的实例jingjing,这样的结果便是有两个引⽤内存中的同⼀个对象Monkey了。当⼀个实例超出作⽤域时,它会从栈中删除,但引⽤对象的数据还是保留在堆中,⼀直到程序终⽌,或垃圾回收器回收它位置,⽽只有该数据不再有任何实例引⽤它时,它才会被删除!
  随便举⼀个实际应⽤引⽤的简单例⼦:
//从界⾯抓取数据放到list中
List<Person> persons = getPersonsFromUI();
//retrieve the persons from DB
List<person> personsFromDB = retrievePersonsFromDB();
//do something to personsFromDB
getSomethingToPersonsFromDB();
  请问对personsFromDB的改变,能在界⾯上及时相应出来吗?
  不能!
 请看下⾯修改代码:
//从界⾯抓取数据放到list中
List<Person> persons = getPersonsFromUI();钱江龙
//retrieve the persons from DB
List<Person> personsFromDB = retrievePersonsFromDB();
int cnt = persons.Count;
for(int i=0;i<cnt;i++)
{
persons[i]= personsFromDB [i] ;
}
//do something to personsFromDB
getSomethingToPersonsFromDB();
 修改后,数据能⽴即响应在界⾯上。因为persons与UI绑定,所有修改在persons上,⾃然可以⽴即响应。
  这就是引⽤数据类型的强⼤之处,在C#中⼴泛使⽤了这个特性。这表明,我们可以对数据的⽣存期进⾏⾮常强⼤的控制,因为只要保持对数据的引⽤,该数据就肯定位于堆上
  这也表明了基于栈的实例与基于堆的对象的⽣存期不匹配!
垃圾回收器 GC
  内存堆上会有碎⽚形成,垃圾回收器会压缩内存堆,移动对象和修改对象的所有引⽤的地址,这是托管的堆与⾮托管的堆的区别之⼀。
  的托管堆只需要读取堆指针的值即可,但是⾮托管的旧堆需要遍历地址链表,找出⼀个地⽅来放置新数据,所以在下实例化对象要快得多。
  堆的第⼀部分称为第0代,这部分驻留了最新的对象。在第0代垃圾回收过程中遗留下来的旧对象放在第1代对应的部分上,依次递归下去。。。
承上启下
  以上部分便是对托管资源的内存管理部分,这些都是在后台由⾃动执⾏的。下⾯看下⾮托管资源的内存管理,⽐如这些资源可能是UI句柄,network连接,⽂件句柄,Image对象等。主要通过三种机制来做这件事。分别为析构函数、IDisposable接⼝,和两者的结合处理⽅法,以此实现最好的处理结果。下⾯分别看⼀下。
析构函数
  C#编译器在编译析构函数时,它会隐式地把析构函数的代码编译为等价于Finalize()⽅法的代码,并确定执⾏⽗类的Finalize()⽅法。看下⾯的代码:
public class Person
{
~Person()怎么煮羊肉
{
//析构实现
}
}
~Person()析构函数⽣成的IL的C#代码:
protected override void Finalize()
{
try
{
//析构实现
}
finally
{
ba.Finalize();
}
}
  放在finally块中确保⽗类的Finalize()⼀定调⽤。
  C#析构函数要⽐C++析构函数的使⽤少很多,因为它的问题是不确定性。在销毁C++对象时,其析
构函数会⽴即执⾏。但由于C#使⽤垃圾回收器,⽆法确定C#对象的析构函数何时执⾏。如果对象占⽤了 宝贵的资源,⽽需要尽快释放资源,此时就不能等待垃圾回收器来释放了。
  第⼀次调⽤析构函数时,有析构函数的对象需要第⼆次调⽤析构函数,才会真正删除对象。如果频繁使⽤析构,对性能的影响⾮常⼤。IDisposable接⼝
  在C#中,推荐使⽤IDisposable接⼝替代析构函数,该模式为释放⾮托管资源提供了确定的机制,⽽不像析构那样何时执⾏不确定。   假定Person对象依赖于某些外部资源,且实现IDisposable接⼝,如果要释放它,可以这样:
class Person:IDisposable
{
public void Dispo()
{
//implementation
pm1}
}
Person xingxing = new Person();
//dom something
xingxing .Dispo();
  上⾯代码如果在处理过程中出现异常,这段代码就没有释放xingxing,所以修改为:
Person xingxing = null;
try
{
xingxing  = new Person();
//do something
}
finally
{
if(xingxing !=null)
{
xingxing.Dispo();
双鱼分手}
}

本文发布于:2023-07-12 22:40:26,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1093264.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:内存   对象   托管   数据   资源   地址   回收
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图