首页 > 代码库 > 黑马公开课——运行原理与GC学习笔记

黑马公开课——运行原理与GC学习笔记

.NET Framework 程序的运行原理

.NET Framework的组成:
(1)基础类库(BCL):使用线程的类来完成编程,对于不存在的类,就自己编写;
(2)编译工具:将源文件,编译成“程序集”(exe或dll等)[.NET环境中,MSIL=CIL=IL]
(3)公共语言运行时(CLR):执行前检测、编译;执行到了某个方法时才编译这个方法的代码[即时编译器(JIT)]
编译过程:.NET源代码(C#)——>通过C#编译器编译成程序集[程序集中包括:元数据(一个表,显示了程序中有什么成员,类,字段,方法等),IL代码等资源]
运行例子:
(1)源代码如下:
using System;

namespace SimpleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine("ByeBye World!");
        }
    }
}
——>程序都是从上到下执行,执行Main方法前要检查Main方法中有什么类,为类分配一个临时的内存空间表。该类(各个类)中每一个方法,都有一个对应的地址(此时地址还是空的)。
——>当执行到第一个Console.WriteLine时:CLR中的JIT找到对应的IL代码,并将其编译成机器码并优化,将编译好的代码放到内存块中,会产生一地址,例如:0x000100
——>开始执行该WriteLine方法实体——>第二次执行WriteLine时:CLR会检查之前的方法表,如果已经存在地址,则不再编译执行。
内存分配:
线程栈:数据地址从高位向低位存放;
托管堆:数据地址从低位向高位存放;
.NET Framework 垃圾回收机制 GC

GC(Garbage Collection)垃圾收集,指的是在.net中垃圾内存收集的机制。

GC原理:当代码创建的时候,对象在内存中“连续”分配;当执行一次垃圾回收以后,失去引用的对象将会被释放;而保持引用的对象会重新“排序”(0代->1代)[重新排序后内存依旧是连续的]

以下是转载自轩脉刃de刀光剑影的博客 原文地址: http://www.cnblogs.com/yjf512/archive/2010/09/14/1825518.html

首先要了解的几点:

  1. 在.net中,托管代码的内存管理是自动的,由GC进行管理。但是对于非托管代码,.net就无法自动管理了。
  2. CLR运行时候,内存分为“托管堆”和“栈”两个部分。其中,栈是用于存储值类型的数据,托管堆是用于存储引用类型的变量。其中托管堆是GC处理的内存部分。
  3. 进程中每个线程都有自己的堆栈。

对于托管代码的GC原理解读:

垃圾判定:

回收垃圾首先要知道什么是垃圾,一个变量如果在其生存期内的某一时刻已经不再被引用,那么,这个对象就有可能成为垃圾。

public static void Main()
              {
                 string sGarbage = "I‘m here";
           //下面的代码没有再引用s,它已经成为垃圾对象---当然,这样的代码本身也是垃圾;
            //此时如果执行垃圾收集,则sGarbage可能已经魂归西天
                  Console.WriteLine("Main() is end");
              }

 

 

对象代龄:

GC认为,越晚创建的对象越短命,所以,其引入一个“代龄”的概念来划分对象生存级别

这个代龄划分机制简要来说是一代新人换旧人:

 

CLR初始化后的第一批被创建的对象被列为0代对象。CLR会为0代对象设定一个容量限制,当创建的对象大小超过这个设定的容量上限时,GC就会开始工作,工作的范围是0代对象所处的内存区域,然后开始搜寻垃圾对象,并释放内存。当GC工作结束后,幸存的对象将被列为第1代对象而保留在第1代对象的区域内。此后新创建的对象将被列为新的一批0代对象,直到0代的内存区域再次被填满,然后会针对0代对象区域进行新一轮的垃圾收集,之后这些0代对象又会列为第1代对象,并入第1代区域内。第1代区域起初也会被设上一个容量限制值,等到第1代对象大小超过了这个限制之后,GC就会扩大战场,对第1代区域也做一次垃圾收集,之后,又一次幸存下来的对象将会提升一个代龄,成为第2代对象。


    可见,有一些对象虽然符合垃圾的所有条件,但它们如果是第1代(甚至是第2代老臣)对象,并且第1代的分配量还小于被设定的限制值时,这些垃圾对象就不会被GC发现,并且可以继续存活下去。

 

 

对于非托管代码,GC不能自动收集垃圾,需要的方法有两种:1,重写让GC自动调用的Finalize方法。 2,实现IDispose提供给我们显示调用的方法Dispose()

  1. Finalize

~ClassName() {//释放你的非托管资源}

Finalize是由GC负责调用,是一种自动释放的方式。

问:为什么说实现了Finalize方法的对象必需等两次GC才能被完全释放?

 技术分享

Msdn中的解释:实现 Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用 Finalize 方法,然后,将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。

        

         第一次的GC做的事情是:1将有终结器的对象放到准备终结列表中,并执行Finalize方法。2 实际删除对象内存。

  1. Dispose

Dispose是提供给我们显示调用的方法。由于对Dispose的实现很容易出现问题,所以在一些书籍上(如《Effective C#》和《Applied Microsoft.Net Framework Programming》)给出了一个特定的实现模式:

class DisposePattern :IDisposable
    {
        private System.IO.FileStream fs = new System.IO.FileStream("test.txt", System.IO.FileMode.Create);

        ~DisposePattern()
        {
            Dispose(false);
        }       

        IDisposable Members#region IDisposable Members

        public void Dispose()
        {
            //告诉GC不需要再调用Finalize方法,
            //因为资源已经被显示清理
            GC.SupdivssFinalize(this);

            Dispose(true);
        }

        #endregion
                
        protected virtual void Dispose(bool disposing)
        {
            //由于Dispose方法可能被多线程调用,
            //所以加锁以确保线程安全
            lock (this)
            {
                if (disposing)
                {
                    //说明对象的Finalize方法并没有被执行,
                    //在这里可以安全的引用其他实现了Finalize方法的对象
                }

                if (fs != null)
                {
                    fs.Dispose();
                    fs = null; //标识资源已经清理,避免多次释放
                }
            }
        }
    }

注: 关键字using()中包含的变量就需要实现了IDispose借口,当出了using的范围的时候会自动使用Dispose方法。

 

黑马公开课——运行原理与GC学习笔记