.net 垃圾回收机制

尽管在.NET framework下我们并不需要担心内存管理和垃圾回收(Garbage Collection),但是我们还是应该了解它们,以优化我们的应用程序。同时,还需要具备一些基础的内存管理工作机制的知识,这样能够有助于解释我们日常程序编写中的变量的行为。在本文中我们将深入理解垃圾回收器,还有如何利用静态类成员来使我们的应用程序更高效。

* 更小的步伐 == 更高效的分配

为了更好地理解为什么更小的足迹会更高效,这需要我们对.NET的内存分配和垃圾回收专研得更深一些。

* 图解:

让我们来仔细看看GC。如果我们需要负责"清除垃圾",那么我们需要拟定一个高效的方案。很显然,我们需要决定哪些东西是垃圾而哪些不是。
为了决定哪些是需要保留的,我们首先假设所有的东西都不是垃圾(墙角里堆着的旧报纸,阁楼里贮藏的废物,壁橱里的所有东西,等等)。假设在我们的生活当中有两位朋友:Joseph Ivan Thomas(JIT)和Cindy Lorraine Richmond(CLR)。Joe和Cindy知道它们在使用什么,而且给了我们一张列表说明了我们需要需要些什么。我们将初始列表称之为"根"列表,因为我们将它用作起始点。我们需要保存一张主列表来记录出我们家中的必备物品。任何能够使必备物品正常工作或使用的东西也将被添加到列表中来(如果我们要看电视,那么就不能扔掉遥控器,所以遥控器将被添加到列表。如果我们要使用电脑,那么键盘和显示器就得添加到列表)。

这就是GC如何保存我们的物品的,它从即时编译器(JIT)和通用语言运行时(CLR)中获得"根"对象引用的列表,然后递归地搜索出其他对象引用来建立一张我们需要保存的物品的图表。

根包括:

* 全局/静态指针。为了使我们的对象不被垃圾回收掉的一种方式是将它们的引用保存在静态变量中。
* 栈上的指针。我们不想丢掉应用程序中需要执行的线程里的东西。
* CPU寄存器指针。托管堆中哪些被CPU寄存器直接指向的内存地址上的东西必须得保留。

在以上图片中,托管堆中的对象1、3、5都被根所引用,其中1和5时直接被引用,而3时在递归查找时被发现的。像我们之前的假设一样,对象1是我们的电视机,对象3是我们的遥控器。在所有对象被递归查找出来之后我们将进入下一步--压缩。

* 压缩

我们现在已经绘制出哪些是我们需要保留的对象,那么我们就能够通过移动"保留对象"来对托管堆进行整理。

幸运的是,在我们的房间里没有必要为了放入别的东西而去清理空间。因为对象2已经不再需要了,所以GC会将对象3移下来,同时修复它指向对象1的指针。

然后,GC将对象5也向下移,

现在所有的东西都被清理干净了,我们只需要写一张便签贴到压缩后的堆上,让Claire(指CLR)知道在哪儿放入新的对象就行了。

理解GC的本质会让我们明白对象的移动是非常费力的。可以看出,假如我们能够减少需要移动的物品大小是非常有意义的,通过更少的拷贝动作能够使我们提升整个GC的处理性能。

* 托管堆之外是怎样的情景呢?

作为负责垃圾回收的人员,有一个容易出现入的问题是在打扫房间时如何处理车里的东西,当我们打扫卫生时,我们需要将所有物品清理干净。那家里的台灯和车里的电池怎么办?

在一些情况下,GC需要执行代码来清理非托管资源(如文件,数据库连接,网络连接等),一种可能的方式是通过finalizer来进行处理。

class Sample


在对象创建期间,所有带有finalizer的对象都将被添加到一个finalizer队列中。对象1、4、5都有finalizer,且都已在finalizer队列当中。让我们来看看当对象2和4在应用程序中不再被引用,且系统正准备进行垃圾回收时会发生些什么。

对象2会像通常情况下那样被垃圾回收器回收,但是当我们处理对象4时,GC发现它存在于finalizer队列中,那么GC就不会回收对象4的内存空间,而是将对象4的finalizer移到一个叫做"freachable"的特殊队列中。

有一个专门的线程来执行freachable队列中的项,对象4的finalizer一旦被该线程所处理,就将从freachable队列中被移除,然后对象4就等待被回收。

因此对象4将存活至下一轮的垃圾回收。

由于在类中添加一个finalizer会增加GC的工作量,这种工作是十分昂贵的,而且会影响垃圾回收的性能和我们的程序。最好只在你确认需要finalizer时才使用它。

在清理非托管资源时有一种更好的方法:在显式地关闭连接时,使用IDisposalbe接口来代替finalizer进行清理工作会更好些。

* IDisposable

实现IDisposable接口的类需要执行Dispose()方法来做清理工作(这个方法是IDisposable接口中唯一的签名)。因此假如我们使用如下的带有finalizer的ResourceUser类:

public class ResourceUser 

{

~ResourceUser() // THIS IS A FINALIZER

{

// DO CLEANUP HERE

          }

}

我们可以使用IDisposable来以更好的方式实现相同的功能:

public class ResourceUser : IDisposable

{

IDisposable Members

}

IDisposable被集成在了using块当中。在using()方法中声明的对象在using块的结尾处将调用Dispose()方法,using块之外该对象将不再被引用,因为它已经被认为是需要进行垃圾回收的对象了。

public static void DoSomething()

{

ResourceUser rec = new ResourceUser();

using (rec)

{

// DO SOMETHING 

} // DISPOSE CALLED HERE

// DON'T ACCESS rec HERE

}

我更喜欢将对象声明放到using块中,因为这样可视化很强,而且rec对象在using块的作用域之外将不再有效。这种模式的写法更符合IDisposable接口的初衷,但这并不是必须的。

public static void DoSomething()

{

using (ResourceUser rec = new ResourceUser())

{

// DO SOMETHING



} // DISPOSE CALLED HERE

}

在类中使用using()块来实现IDisposable接口,能够使我们在清理垃圾对象时不需要写额外的代码来强制GC回收我们的对象。

* 静态方法

静态方法属于一种类型,而不是对象的实例,它允许创建能够被类所共享的方法,且能够达到"减肥"的效果,因为只有静态方法的指针(8 bytes)在内存当中移动。静态方法实体仅在应用程序生命周期的早期被一次性加载,而不是在我们的类实例中生成。当然,方法越大那么将其作为静态就越高效。假如我们的方法很小(小于8 bytes),那么将其作为静态方法反而会影响性能,因为这时指针比它指向的方法所占的空间还大些。

接着来看看例子...

我们的类中有一个公共的方法SayHello():

class Dude

{

private string _Name = "Don";



public void SayHello()

{

                    Console.WriteLine(this._Name + " says Hello");

          }

}

在每一个Dude类实例中SayHello()方法都会占用内存空间。

一种更高效的方式是采用静态方法,这样我们只需要在内存中放置唯一的SayHello()方法,而不论存在多少个Dude类实例。因为静态成员不是实例成员,我们不能使用this指针来进行方法的引用。

class Dude

{

private string _Name = "Don";



public static void SayHello(string pName)

{

                    Console.WriteLine(pName + " says Hello");

          }

}

请注意我们在传递变量时栈上发生了些什么(可以参看<第二部分>)。我们需要通过例子的看看是否需要使用静态方法来提升性能。例如,一个静态方法需要很多参数而且没有什么复杂的逻辑,那么在使用静态方法时我们可能会降低性能。

* 静态变量:注意了!

对于静态变量,有两件事情我们需要注意。假如我们的类中有一个静态方法用于返回一个唯一值,而下面的实现会造成bug:

class Counter

{

private static int s_Number = 0;

public static int GetNextNumber()

{

int newNumber = s_Number;

// DO SOME STUFF        

                    s_Number = newNumber + 1;

return newNumber;

          }

}

假如有两个线程同时调用GetNextNumber()方法,而且它们在s_Number的值增加前都为newNumber分配了相同的值,那么它们将返回同样的结果!

我们需要显示地为方法中的静态变量锁住读/写内存的操作,以保证同一时刻只有一个线程能够执行它们。线程管理是一个非常大的主题,而且有很多途径可以解决线程同步的问题。使用lock关键字能让代码块在同一时刻仅能够被一个线程访问。一种好的习惯是,你应该尽量锁较短的代码,因为在程序执行lock代码块时所有线程都要进入等待队列,这是非常低效的。

class Counter

{

private static int s_Number = 0;

public static int GetNextNumber()

{

lock (typeof(Counter))

{

int newNumber = s_Number;

// DO SOME STUFF

                             newNumber += 1;

                             s_Number = newNumber;

return newNumber;

                    }

          }

}

* 静态变量:再次注意了!

静态变量引用需要注意的另一件事情是:记住,被"root"引用的事物是不会被GC清理掉的。我遇到过的一个最烦人的例子:

class Olympics

{

public static Collection<Runner> TryoutRunners;

}


class Runner

{

private string _fileName;

private FileStream _fStream;

public void GetStats()

{

                    FileInfo fInfo = new FileInfo(_fileName);

                    _fStream = _fileName.OpenRead();

          }

}

由于Runner集合在Olympics类中是静态的,不仅集合中的对象不会被GC释放(它们都直接被根所引用),而且你可能注意到了,每次执行GetStats()方法时都会为那个文件开放一个文件流,因为它没有被关闭所以也不会被GC释放,这个代码将会给系统造成很大的灾难。假如我们有100000个运动员来参加奥林匹克,那么会由于太多不可回收的对象而难以释放内存。天啦,多差劲的性能呀!

* Singleton

有一种方法可以保证一个类的实例在内存中始终保持唯一,我们可以采用Gof中的Singleton模式。(Gof:Gang of Four,一部非常具有代表性的设计模式书籍的作者别称,归纳了23种常用的设计模式)

public class Earth

{

private static Earth _instance = new Earth();

private Earth() { }

public static Earth GetInstance() { return _instance; }

}

我们的Earth类有一个私有构造器,所以Earth类能够执行它的构造器来创建一个Earth实例。我们有一个Earth类的静态实例,还有一个静态方法来获得这个实例。这种特殊的实现是线程安全的,因为CLR保证了静态变量的创建是线程安全的。这是我认为在C#中实现singleton模式最为明智的方式。

* .NET Framework 2.0中的静态类

在.NET 2.0 Framework中我们有一种静态类,此类中的所有成员都是静态的。这中特性对于工具类是非常有用的,而且能够节省内存空间,因为该类只存在于内存中的某个地方,不能在任何情况下被实例化。

* 总结一下...

总的来说,我们能够提升GC表现的方式有:

1. 清理工作。不要让资源一直打开!尽可能地保证关闭所有打开的连接,清除所有非托管的资源。当使用非托管对象时,初始化工作尽量完些,清理工作要尽量及时点。

2. 不要过度地引用。需要时才使用引用对象,记住,如果你的对象是活动着的,所有被它引用的对象都不会被垃圾回收。当我们想清理一些类所引用的事物,可以通过将这些引用设置为null来移除它们。我喜欢采用的一种方式是将未使用的引用指向一个轻量级的NullObject来避免产生null引用的异常。在GC进行垃圾回收时,更少的引用将减少映射处理的压力。

3. 少使用finalizer。Finalizer在垃圾回收时是非常昂贵的资源,我们应该只在必要时使用。如果我们使用IDisposable来代替finalizer会更高效些,因为我们的对象能够直接被GC回收而不是在第二次回收时进行。

4. 尽量保持对象和它们的子对象在一块儿。GC在复制大块内存数据来放到一起时是很容易的,而复制堆中的碎片是很费劲的,所以当我们声明一个包含许多其他对象的对象时,我们应该在初始化时尽量让他们在一块儿。

5. 最后,使用静态方法来保持对象的轻便也是可行的。

转载于:https://www.cnblogs.com/as_as/archive/2010/07/13/1776405.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304104.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《自然》杂志:中国人越来越沉迷于对着一个叫“区块链”的东西胡言乱语

起初&#xff0c;《自然》杂志以为在2018年春节前后中国发生了一场瘟疫&#xff0c;但很快就改变了这一看法。除了精神亢奋无法入睡&#xff0c;那里的人们身体还算健康。不过&#xff0c;他们越来越沉迷于对着一个叫“区块链”的东西胡言乱语&#xff0c;根本停不下来。因为教…

python if 跳出_Python保留字简单释义

作者&#xff1a;小小程序员链接&#xff1a;https://zhuanlan.zhihu.com/p/87393696来源&#xff1a;知乎著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。Guido van Rossum在1991年正式对外发布Python版本&#xff0c;现在已成为最流行的语言…

客官,.NETCore无代码侵入的模型验证了解下

.NETCore下的模型验证相信绝大部分的.NET开发者或多或少的都用过&#xff0c;微软官方提供的模型验证相关的类位于System.ComponentModel.DataAnnotations命令空间下&#xff0c;在使用的时候只需要给属性添加不同的特性即可实现对应的模型验证。如下所示&#xff1a;public cl…

敏捷个人:提供更多文档下载,并转载一篇敏捷个人读书笔记

这两周一直忙着OpenExpressApp的自动化测试支持了&#xff0c;对于敏捷个人最近在思考作为新手如何学习的问题&#xff0c;后期我会写篇blog与大家分享一下。在敏捷个人项目中我发布了敏捷个人&#xff0d;认识自我&#xff0c;管理自我.pdf&#xff0c;有很多朋友之前看过&…

大数据揭秘:低学历者发财的概率有多大?结果很吃惊

先看两幅图&#xff1a;Table 1: Mean Earnings by Highest Degree Earned, $: 2009 (SAUS, table 232)Table 2: Unemployment Rates by Educational Attainment图一是美国社会收入和最高学历的关系&#xff0c;图二是美国社会失业率和受教育程度的关系&#xff0c;数据来自SAU…

多个cpp文件生成so_boostpython:从多个.cpp文件创建一个模块(.so)

我开始在C中编写一些我想在Python代码中调用的模块。为此&#xff0c;我使用boostpython。随着代码的增长&#xff0c;我决定将其分成几个.cpp文件。现在&#xff0c;我有了这样的东西&#xff1a;食品.cpp#include "Bar.hpp"#include "Baz.hpp"#include u…

SSH远程终端连接数问题

系统 linux &#xff08;Debian&#xff09; 存在问题&#xff1a;SSH终端连接数最大为10个 解决方案&#xff1a; 1) 修改/etc/ssh/sshd_config中#MaxStartups 10:30:60&#xff0c;将其改为MaxStartups 1000 2) 重启SSH服务&#xff0c;/etc/init.d/ssh restar…

Win10 Terminal + WSL 2 安装配置指南

自从 Windows Terminal 正式发布后就再没有用过 Windows 系统自带的终端了。主要是 Terminal 简洁且灵活&#xff0c;更重要的是支持特殊字体&#xff0c;通过一些简单的配置可以使得终端看起来更舒适养眼。自从 Win 10 有了 Linux 子系统&#xff08;WSL&#xff09;&#xff…

如何快速解剖数据背后隐藏的信息

1946年2月16日&#xff0c;是一个值得纪念的日子。在这一天&#xff0c;人类历史上真正意义上的第一台电子计算机诞生了&#xff0c;此后计算机便随着科技的发展以强大的生命力飞速发展着。而作为用来定义计算机程序的形式语言——编程语言也紧跟计算机其后蓬勃发展&#xff0c…

mysql改密码脚本_mysql密码修改脚本

网上搜索&#xff1a; mysql密码修改工具 title 护卫神MySQL密码修改工具 echo off color 0a ECHO ┏━━━━━━━━━━┥ 护卫神www.huweishen.com ┝━━━━━━━━━┓ ECHO ┃ 提示: ┃ E…

来,Consul 服务发现入个门(一看就会的那种)

前言在微服务架构中&#xff0c;对于一个系统&#xff0c;会划分出多个微服务&#xff0c;而且都是独立开发、独立部署&#xff0c;最后聚合在一起形成一个系统提供服务。当服务数量增多时&#xff0c;这些小服务怎么管理&#xff1f;调用方又怎么能确定服务的IP和端口&#xf…

深入理解alias, alias_method和alias_method_chain

对于alias, alias_method, alias_method_chain的深入理解是有益的&#xff0c;因为rails3的源码里很多地方使用了alias_method_chain的魔法。 有人评论说alias_method_chain使用的过多不好&#xff0c;具体怎么不好&#xff0c;是后话了&#xff0c;这篇文章集中在理解这3个方法…

mysql数据库的安装和配置文件_MySQL 数据库安装与配置详解

目录一、概述MySQL 版本&#xff1a;5.7.17客户端工具&#xff1a;NavicatforMySQL** 二、MySQL 安装**安装条件&#xff1a;如果 Windows Server 2003 在安装.net framework4.0 安装过程中报错&#xff1a; net framework 4.0 安装时提示产生阻滞问题:运行安装程序前&#xff…

Magicodes.IE Excel合并行数据导入教程

说明Magicodes.IE.Excel目前已支持合并行单元格导入&#xff0c;如本篇教程所示。安装包Magicodes.IE.ExcelInstall-PackageMagicodes.IE.Excel添加Dto参考示例代码如下所示&#xff1a;public class MergeRowsImportDto {[ImporterHeader(Name "学号")]public long…

2010.7.27 OnDraw与OnPaint有什么区别

引用&#xff1a;http://wenku.baidu.com/view/bc9b1c661ed9ad51f01df2ab.html OnPaint是WM_PAINT消息的消息处理函数&#xff0c;在OnPaint中调用OnDraw&#xff0c;一般来说&#xff0c;用户自己的绘图代码应放在OnDraw中。 OnPaint()是CWnd的类成员&#xff0c;负责响应WM_P…

D轮融资1亿美金,6亿美金估值,3位计算机学霸如何带领海归团队创造业内神话?!

顺为资本在创始合伙人雷军及许达来的带领下成功领投了51Talk、丁香园、爱奇艺、一起作业等超级公司有近20家公司估值超过10亿美元如今顺为资本为何愿意投资这家公司&#xff1f;酷家乐6年破6亿美金的公司估值17年全年营收超3亿有着设计师300万这家以家居云设计为核心的创业公司…

linux下mysql案例_Linux下安装MySQL多实例

环境说明&#xff1a;Centos 6.6 64位mysql 使用最新版本5.7.16版本这里安装两个MySQL实例&#xff0c;分别使用3306/3307端口号目录结构&#xff1a;/data/mysql/mysql3306/data/mysql/mysql3306/data/data/mysql/mysql3307/log/data/mysql/mysql3306/tmp执行命令&#xff1a;…

Navicat

作为Oracle, MySQL, Sqlite, PostgreSQL的统一客户端&#xff0c;Navicat无疑是最方便简洁的&#xff0c;而且界面非常友好。导入导出支持的格式也很全。 在连Oracle的时候&#xff0c;会报错说字符集不支持。只要手工指定OCI就好&#xff0c;方法如下&#xff1a; 在OCI libra…

分布式链路追踪框架的基本实现原理

目录分布式追踪分布式系统分布式追踪分布式追踪有什么用呢什么是分布式追踪Dapper分布式追踪系统的实现跟踪树和 spanJaeger 和 OpenTracingOpenTracing 数据模型Span 格式TraceOpenTracingJaeger 结构SpanOpenTracing API分布式追踪什么是分布式追踪分布式系统当我们使用 Goog…

mysql外键约束创建及删除_MySQL中的外键的创建,约束和删除

一、外键的创建语法一&#xff1a;后续添加方法alter table 表名 add constraint 约束名 foreign key(当前表中约束的字段) references 主表表名(要约束的字段名);alter table student add constraint fk_class_student foreign key(cls_id) class(cls_id) on update cascade o…