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

news/2024/7/20 14:00:33 标签: c#, 内存管理

.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才能被完全释放?

 有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方法。

 

 


http://www.niftyadmin.cn/n/1693415.html

相关文章

学习笔记-异常检测(Anormaly Detection)

(这一章已经学了好常一段时间了,现在才有时间来写总结,全凭记忆了) 问题由来 检测发动机是否正常 我们已有一些针对发动机检测的指标,和这些指标对应的检测值,我们需要根据一批新的检测值,确…

学习笔记-Large scale machine learning

终于来到了倒数第二周,这章的主要内容是怎样应对超大样本量的机器学习项目,例如训练集有500万个样本。 之前我们曾提到,有时候样本量的多少决定了一个模型的好坏,当样本量达到一定数量级时,应用不同算法获得的结果差不…

学习笔记-图像识别(Photo OCR)

The photo OCR problem Photo OCR 全称 Photo Optical Character Recognition,即图像识别。例如识别下图中的文字: 文字识别的一般步骤如下: 1. 检测文字所在的区域; 2. 字符拆分; 3. 字符识别。 Sliding wi…

扩展方法的几个实例,扩展基本类型、接口、通过反射让扩展方法使用私有成员等...

.net扩展方法可以扩展很多类型,包括:基本数据类型、接口、类,等等。如果,需要扩展的类型包含私有成员,扩展方法如何运用这些私有成员呢?本篇逐一体验,包括: ■ 扩展基本数据类型■ 扩…

Visual Studio 2012 调试程序加载缓慢,提示正在下载符号

当在Visual Studio 2012中调试程序的时候,整个过程加载缓慢,并且有类似"正在下载符号......"的提示。 这是因为Visual Studio在调试的时候,默认从Microsoft符号服务器下载pdb文件,通过如下方式去除默认设置。 1、工具--选项--调试-…

学习笔记-神经网络初级入门

1. 写作背景 ——刚学到的知识如果不及时记录下来,很快就会被遗忘。 从上个月开始我就开始在Coursera平台学习吴恩达老师开设的深度学习课程。今天我已经学完第一门课程,并已经获得证书。这门课程不像之前的机器学习课程提供了完整的授课ppt&#xff0…

咏南IOCP中间件

咏南IOCP中间件 特大好消息,咏南中间件系列新增加——咏南IOCP中间件。咏南IOCP中间件完全兼容咏南DATASNAP中间件的远程方法接口。 中间件DELPHI7~DELPHI XE10.1.1都能编译。 中间件使用UNIDAC数据数据,可以驱动市面上几乎所有的数据库。 客户端开发支持…

利用R画置信椭圆

利用R绘制置信椭圆(confidence ellipse) 有时候我们需要查看两组数据的分布情况,这时候通过可以绘制置信椭圆达到目的。 library(car) dataEllipse(Duncan$income, Duncan$education, levels0.95,xlimc(-30,120),ylimc(-30,160)) 上述代码可以很快速地画出income…