值数据类型:
{
int a;
// do something
{
int b;
//do something
}
}
首选声明了 a ,在内部代码块中声明了 b 。程序运行时,内部代码块终止,b 就超出了作用域,最后 a 超出了 作用域。所以 b 的生存期完全包含在 a 的生存期中。在释放变量时,其顺序总是与给它们分配内存的顺序相反,这就是栈的工作方式。 还要注意 b 在另一个代码块中 。因此它包含在另一个作用域中,这称为块级作用域或结构作用域
{
int n=10;
double d=300.01;
//do calculations.
}
n 变量进入作用域,赋值为 10,这个值存放在存储单元 7999996 ~ 7999999 上,这4个字节就在栈指针所指的空间下面。变量的数据类型,决定了栈指针移动的步长。int 类型为4字节,为了容纳 int 类型的数据,应从栈指针对应的值(地址)中减去4 ,所以现在指向的位置是 7999996,而下一个空闲内存单元是 7999995。下一行代码声明了一个 double 类型的变量 d ,初始化为 300.01。double 类型占 8 个字节。所以在栈上的存储单元为 7999988 ~ 7999995 上,栈指针对应的值减去 8 再次指向栈上的下一个空闲存储单元。
当变量 b 超出作用域时,运行库(CLR)就知道不需要这个变量了。因为变量的生存期总是嵌套的,当 d 在作用域时,无论发生什么情况,都可以保证栈指针总是指向存储 d 的空间。为了从内存中删除这个变量,应该给栈指针的值递增 8 ,指向 变量 d 末尾相邻的空间。当 变量 a 也超出作用域时,栈指针应该再次递增 4 。从栈中删除 变量 a 和 d 后,如果此时在作用域中由放如另一个变量,从地址 7999999 开始的存储单元就会被覆盖掉。
如果编译器遇到 int i,j;
这样的代码,则这两个变量进入作用域的顺序是不确定的。两个变量是同时声明的,也是同时超出作用域。此时,变量以什么顺序从内存中删除就不重要了。编译器会确保先放入内存中的那个变量后删除,这样就会保证该规则不会与变量的生存期冲突。
void DoWork()
{
Custome arable;
arable= new Custome();
Custome otherCustome2= new EahancedCustome();
}
上面代码:假定两个类 Custome 和 EahancedCustome。EahancedCustome 扩展了 Custome 类。 首先 Custome arable;
声明了一个 Custome 对象的引用 arable(变量),在栈上给这个引用分配存储空间,但这只是只是引用(一个地址),而不是实际的 Custome 对象。arable 引用占用4个字节存储引用地址。 然后下一行 arable= new Custome();
代码完成以下操作:首先,它分配堆上的内存,存储 Custome 对象(一个真正的对象,而不是地址),然后把变量 arable 的值 重新分配给新对象 Custome 的内存地址。
Custome 对象的值放在堆中,而栈中存放的只是指向 堆存储空间的地址。
应用程序 new 操作符创建对象时,如果内有足够的地址空间来分配对象,发现空间不足时,CLR就会执行垃圾回收。
一般情况下,垃圾回收器在 .NET 运行库任务需要进行垃圾回收时运行。可以调用 System.GC.Collect() 方法,强迫垃圾回收器在代码的某个地方运行,System.GC 类是一个 .NET 中表示垃圾回收的类,Collect() 方法启动一个垃圾回收过程。但是 GC 类使用的场合很少。例如:代码中有大量的对象刚刚取消引用,就适合调用垃圾回收器。但是,垃圾回收器的逻辑不能保证在一次垃圾回收过程中,所有未引用的对象都从堆中删除。
CLR 的 GC 是基于代的垃圾回收器(generational garbage collector), 它对代码做出以下几点假设:
详情:
托管堆在初始化时不包含对象。添加到堆的对象称为第 0 代对象。简而言之,第 0 代对象就是那些新构造的对象,垃圾回收器从未检查过它们。如下图:新启动的程序分配 5 个对象 (A 到 E)程序运行一会 对象 C 和 E 变得不可达(失去引用)
CLR 初始化时为 第 0 代 对象选择一个预算容量(以 KB 为单位)。如果分配一个新对象造成第 0 代超过预算,就必须启动一次垃圾回收。假设 A 到 E 刚好用完 第 0 代空间,那么分配对象 F 就必须启动垃圾回收。垃圾回收器判断对象 C 和 E 是垃圾,所以会压缩对象 D ,使之与对象 B 相邻。在垃圾回收中存活的对象(A ,B,和D)现在称为第 1 代对象。第 1 代对象已经经历了一次垃圾回收器的检查。如下图:
一次垃圾回收后,第 0 代就不包含任何对象了。和前面一样,新对象会分配到第 0 代中。如果程序继续运行,并新分配了对象 F 到 K 。另外 ,随着程序再次运行,对象 B ,H 和 J 变得不可达(失去引用),它们的内存将在某一时刻回收。如下图:
现在,假定第 0 代空间已满, 分配新对象L 会造成第 0 代超出预算,就必须启动垃圾回收。开始启动垃圾回收时,垃圾回收器就必须检查哪些代。前面说过,CLR 初始化时会为第0代对象选择预算。事实上,它还必须为第 1 代 选择预算。
开始一次垃圾回收时,垃圾回收器还会检查第 1 代 占用多少内存。在本例中由于第1 代占用的内存远少于预算,所以垃圾回收器只检查第 0 代中的对象。回顾上面基于代 垃圾回收器做出的假设,第一是越新的对象活的越短。因此,第 0 代包含更多垃圾的可能性很大,能回收更多的内存。由于忽落了第 1 代,加快了垃圾回收的速度。
显然,忽落第 1 代中的对象能提升垃圾回收器的性能。但对性能更大的提升作用的是现在不必便利托管堆中的每一个对象。如果根或对象引用了老一代的某个对象,垃圾回收器就可以忽落老对象内部的所有引用,能在更短的时间内构造好可达对象图(graph of reachable object)。当然,老对象的字段也有可能引用新对象。为了确保对老对象的已更新字段进行检查,垃圾回收器利用 JIT 编译器内部的一个机制。这个机制在对象的引用字段发生变换时,会设置一个对应的位标志。这样,垃圾回收器就知道自上一次垃圾回收以来。那些老对象(如果有的话)已被写入。只有字段发生变换的老对象才需要检查是否引用了第 0 代中的任何新对象。
基于代的垃圾回收器还假设越老的对象活的越长。也就是说,第 1 代对象在应用程序中很有可能是继续可达的。如果垃圾回收器检查第 1 代 对象,很有可能找不到垃圾,结果是回收不了多少内存。因此,对第 1 代进行垃圾回收很可能是浪费时间。如果真有垃圾在第 1 代中,它将留在那里。如下图:
如上图所示,所幸存下来的第 0 代都称为 第 1 代的一部分,由于垃圾回收器没有检查第 1 代,所以对象 B 的内存并没有被回收,即使它在上一此垃圾回收时已经不可达。同样在一此垃圾回收后,第 0 代不包含任何对象,等待分配新对象。假定程序继续运行,并分配对象 L 到 O 。另外 对象 G,L和M 变得不可达(失去引用)。此时 如下图:
假设再次分配对象 P 导致第 0 代超过预算,垃圾回收发生。由于第 1 代中所有对象占据的内存仍小于预算,所以垃圾回收器再次回收第 0 代 ,忽落第 1 代 中的垃圾对象(B和G),回收后如下:
从上面可以看出,第 1 代正缓慢增长。假定第 1 代的增长导致它的存储空间超出预算,这时,程序继续运行(因为垃圾回收刚刚完成),并分配对象 P 到 对象 S ,并使 第 0 代 对象达到它的预算容量。
如下:
应用程序在此分配对象 T 时, 由于第 0 代已满,所以必须进行垃圾回收。但这一次垃圾回收器发现 第 1 代占用了太多内存,以至于预算用完。由于前几次对第 0 代进行垃圾回收时,第 1 对可能有许多对象变得不可达(失去引用)。所以垃圾回收器决定检查第 1 代 和 第 0 代中的所有对象。两代都被垃圾回收后,堆的情况如下:
和之前一样,经过垃圾回收后,第 0 代的幸存者被提升为 第 1 代,第 1 代的幸存者被提升为第2代,第 0 代 再次空出,准备接受新对象。第 2 代中的对象经过2此或更多此检查。虽然到目前位置发生过多次垃圾回收,但只有第 1 代 超出预算时才会检查第 1 代中的对象。而再次之前,一般已经对第 0 代进行了好几次垃圾回收。
托管堆只支持三代:第 0 代,第 1 代,第 2 代。没有第 3 代。CLR 初始化会为每一代选择预算。然而 CLR的垃圾回收器是自调节的。这意味着垃圾回收器会在执行垃圾回收的过程中了解应用程序的行为。例如:假定应用程序构造了许多对象,但每个对象用的时间都很短。在这种情况下,对第 0 代的垃圾回收会回收大量内存。事实上,第 0 代所有对象都可能被回收。如果垃圾回收器发现在回收 0 代后存活下来的对象很少,就可能减少第 0 代的预算。已分配空间的减少意味着垃圾回收更频繁地发生,但是垃圾回收器每次做的事情也减少了,这减少了进程的工作集。事实上,如果第 0 代中的所有对象都是垃圾,垃圾回收就不必压缩(移动)任何内存;只须让 NextObjPrt 指针指回第 0 代的起始处即可。这样回收垃圾更让快!!!
另一方面,如果垃圾回收器回收了第 0 代,发现还有很多对象存活,没有对少内存被回收,就会增大第 0 代的预算。现在,垃圾回收的次数将减少,但每次进行垃圾回收时,回收的内存要多得多。但是,如果内有回收到足够的内存,垃圾回收器会执行一次完整的回收,如果还不够,就会抛出 OutOfMemoryException 异常。
到目前为止,只是讨论了每次垃圾回收后如何动态调整第 0 代的预算。但垃圾回收器还用类似的启发式算法调整第 1 代 和 第 2 代 的预算。这些代被垃圾回收时,垃圾回收器会检查有多少内存被回收,以及多少对象幸存。基于这些结果,垃圾回收器可能增大或减少这些代的预算,从而提升应用程序的总体性能。最终实现垃圾回收器会根据应用程序要求的内存负载来字段优化。
C# 中的每个类型都代表一种资源,而资源有分为两类:
public class ClassDome:IDisposable
{
public int Count { get; set; }
public string Str { get; set; }
private IntPtr handle;
public StreamReader reader =null;
private bool isDisposed = false;
public ClassDome(IntPtr handle)
{
Count = 1;
Str = "AAA";
this.handle = handle;
reader = new StreamReader(@"D:\T.txt", Encoding.UTF8);
}
///
/// 析构函数
///
~ClassDome()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!isDisposed)
{
if(disposing)
{
//通过调用托管对象的Dispose()方法来清除托管对象
this.Str = null;
reader.Close();
reader.Dispose();
}
//清理非托管对象
CloseHandle(handle);
handle = IntPtr.Zero;
}
this.isDisposed = true;
}
#region 清理非托管资源
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
#endregion
#region
[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
///
/// 释放内存
///
public static void ClearMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
ClassDome.SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
#endregion
}