.net框架读书笔记---CLR内存管理\垃圾收集(二)

  前几天学习了CLR垃圾收集原理和基本算法,但是那些是仅仅相对于托管堆而言的,任何非托管资源的类型,例如文件、网络资源等,都必须支持一种称为终止化(finalization)的操作。

终止化

终止化操作允许一种资源在他所占的内存被回收之前首先执行一些清理工作。要提供终止化操作操作,必须为类型实现一个名为Finalize的方法。当垃圾收集器判定一个对象为可收集的垃圾时,它便会调用该对象的Finalize方法(如果存在的话)。

  C#为定义Finalize方法提供了特殊的语法看下面代码;

ContractedBlock.gifExpandedBlockStart.gif代码
public class OSHandler
{
private IntPtr handler;

public OSHandler(IntPtr handler)
{
handler
= handler;
}

//当垃圾收集器执行时,该析构函数将被调用,它将关闭非托管资源句柄
~OSHandler()
{
CloseHandler(handler);
}

public IntPtr ToHandler()
{
return handler;
}

//释放非托管资源
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static bool CloseHandler(IntPtr handler);
}

查看中间语言

 

ContractedBlock.gifExpandedBlockStart.gif代码
.method family hidebysig virtual instance void
Finalize() cil managed
{
// 代码大小 26 (0x1a)
.maxstack 1
.
try
{
IL_0000: nop
IL_0001: ldarg.
0
IL_0002: ldfld native
int FinalizeStudy.OSHandler::'handler'
IL_0007: call
bool FinalizeStudy.OSHandler::CloseHandler(native int)
IL_000c: pop
IL_000d: nop
IL_000e: leave.s IL_0018
}
// end .try
finally
{
IL_0010: ldarg.
0
IL_0011: call instance
void [mscorlib]System.Object::Finalize()
IL_0016: nop
IL_0017: endfinally
}
// end handler
IL_0018: nop
IL_0019: ret
}
// end of method OSHandler::Finalize

 

会发现,析构函数被编译器编译为Finalize函数,并且使用了异常处理。

  这样当未来某个时刻垃圾收集器判定对象为可收集的垃圾时,它会看到该类型定义有一个Finalize方法,于是它便会调用该方法,从而允许CLoseHandler函数来关闭其中的非托管资源。在Finalize方法返回之后的某个时刻,该OSHandler对象在托管堆中所占的内存才会被回收。

  应该避免使用Finalize方法。有以下原因:

  • 实现了Finalize的对象其代龄会被提高,增加内存的压力,声音被该对象直接或者间接引用的对象的代龄也将被提升(以后学习代龄)。
  • 终止化对象的分配花费的时间较长,因为指向它们的指针必须被放在终止化链表上;
  • 强制垃圾收集器执行Finalize方法会极大的损失程序的性能;
  • 不能控制Finalize方法何时执行。对象可能会一直占有着资源,直到出现垃圾收集;
  • CLR不对Finalize方法的执行顺序做任何的保障。加入对象包含指向另一个对象的指针,两个对象都可能会被垃圾收集,顺序的不一样会导致结果不可预期。靠,个人感觉这就是一个bug。

终止化操作的内部机理

  创建一个新对象,new先为对象在托管堆上面分配内存。如果对象的类型定义了FInalize方法,那么在该类型的实例被调用之前,指向该对象的一个指针将被放到一个称为终止化链表(finalization list)的数据结构里面。终止化链表是一个由垃圾收集器控制的内部数据结构。链表上的每一个条目都引用着一个对象。这实际告诉垃圾收集器在回收这些对象的内存之前要首先调用它们的Finalize方法。

  当垃圾收集检测到可收集的垃圾时,垃圾收集器会扫描终止化链表是否有执行可收集垃圾的对象,当找到这样的指针,它们会从终止化链表移除,并添加到一个称为终止化可达列表(freachable queue)的数据结构上。在终止化可达列表上出现的对象表示该对象的Finalize方法即将被调用,当垃圾收集完毕后,没有Finalize的对象的内存将被回收,实现了Finalize的对象内存却不能被回收,因为他们的Finalize方法还没有被调用。CLR有一个特殊的高优先级的线程用来专门调用Finalize方法。该线程可以避免线程同步问题。

  非常有意义的是,当垃圾收集器将一个对象从终止化链表转移到终止化可达队列时,该对象不再认为是可收集的垃圾对象,它的内存也就不可能被回收。到此为止,垃圾收集器完成了垃圾对象的鉴别工作,一些原先认为是垃圾的对象现在被认为不是垃圾,从某种意义上来说,对象又“复苏”了。当第一次垃圾收集执行完毕后,特殊的CLR线程将会清空终止化可达队列中的对象,同时执行其中某个对象的Finalize方法。

  等下一次垃圾收集执行的时候,它会看到这些终止化对象已经成为真正的垃圾对象,这样实现了Finalize的对象的内存才被完全回收。 实际上终止化对象需要执行两次垃圾收集才能释放它所占用的内存。实际上由于代龄的提高,可能收集次数会多于两次。上面这些玩意在Effective C#里面也讲过,以前没有看懂。

 

Dispose模式

  感觉CLR的终止化是个吃力不讨好的玩意

  • 分配起来慢(加入终止化链表),
  • 收集起来更慢,先是加入可达终止化列表,让对象复活,二次垃圾回收才能收集;
  • 不能人为的控制,长时间占用内存;
  • 增加对象的代领,更是不可饶恕。
  • 怎么办??

  微软总是NB的,作者总是掉人胃口的,CLR提供了显示释放或者关闭对象的能力,但是类型需要实现一种被称为Dispose的模式(当然有一些约定)。如果一个类型实现了Dispose模式,使用该类型的开发人员将能够知道当对象不再被使用时如何显式地释放掉它所占用的资源。

   新版本的OSHandler实现,应用了Dispose模式:

  

ContractedBlock.gifExpandedBlockStart.gif代码
public class OSHandler:IDisposable
{
private IntPtr handler;

public OSHandler(IntPtr handler)
{
this.handler = handler;
}

//当垃圾收集器执行时,该析构函数将被调用,它将关闭非托管资源句柄
~OSHandler()
{
Dispose(
false);
}

public IntPtr ToHandler()
{
return handler;
}

//释放非托管资源
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static bool CloseHandler(IntPtr handler);


public void Dispose()
{
//因为对象的资源被显示清理,所以在这里阻止垃圾收集器调用Finalize方法
GC.SuppressFinalize(this);
//进行实际清理工作
Dispose(true);

}

//可以替换Dispose方法
public void Close()
{
Dispose();
}

//执行清理工作,protected为了子类
protected void Dispose(bool disposing)
{
//线程安全
lock (this)
{
if (disposing)
{
//对象正在被被显式关闭,此时可以引用其他对象,因为Finalize方法还没有被执行
}
}
if(IsValid)
{
//如果handler有效,那么关闭之
CloseHandler(handler);

handler
=InvalidHandler;//置为无效,防止多次调用
}
}
//返回一个无效的句柄值
public IntPtr InvalidHandler{get{ return IntPtr.Zero;}}

//判断句柄是否有效
public bool IsValid { get { return handler != InvalidHandler; } }
}

  调用上面的Dispose或者Close方法只是显示释放非托管资源,并不会释放托管堆中占用的内存,释放对象内存的工作仍然由垃圾收集器负责,当然释放时间仍然是不确定的。 

  上面的代码中Finalize中Dispose方法的disposing参数被设为fasle。这将告诉Dispose方法不应该执行任何其他对象的代码。在Close和无参Dispose方法中disposing参数为true,因为是手动执行,程序逻辑可以控制,可以在if中执行代码。调用SuppressFinalize主要是为了避免终止化对象给垃圾收集器带来负担。

  既然已经有了手动关闭的方法,为什么还要实现Finalize方法呢,因为我们不能保证程序的使用者能够一定调用Dispose方法或者Close方法,如果不调用将会造成资源浪费,甚至系统崩溃,但是这不是使用者的错误,我们的程序应该考虑到这一点,实现Finalize就是为了防止这种情况出现,作为一个后备吧。

睡觉~~~~~~~~~~~~

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

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

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

相关文章

重构手法——提炼函数、搬移函数、以多态取代条件表达式

目录我的心路历程我的学习概括Extract Method(提炼函数)动机*--做法动机--做法*Move Method(搬移函数)动机*--做法动机--做法*Replace Conditional with Polymorphism(以多态取代条件表达式)动机*--做法动机…

FTP服务器架设详细图解

FTP是File Transfer Protocol(文件传输协议)的缩写,用来在两台计算机之间互相传送文件。FTP服务作为Internet最古老的服务之一,无论在过去还是现在都有着不可替代的作用。在企业中,对于一些大文件的共享,通…

IP-tools

IP-tools 网管员的第三只眼^ Ip-tools是一款功能齐全的网管软件,可以随时随地的向网管员报告网络的运行情况ip-tools自身集成多种tcp/ip使用工具,如本地信息、链接信息、端口扫描、ping、WHOIS、finger、nslookup、telnet、NetBIOS等功能。界面是全英的&…

用git提交代码到远程仓库遇到的问题

目录我的学习过程git环境配置(Mac版)git原理图git的push操作思路遇到的问题我的学习过程 昨天重写了一遍聊天程序,准备提交到git上进行代码管理。结果遇到了不少问题。我照着网上的教程进行操作,一步一步踩了很多坑。 git环境配…

git pull和push整理和归纳

目录各个模块概念工作区版本库暂存区远程仓库pull和push流程相关的命令暂存区相关版本库相关远程仓库相关利用远程仓库协作开发各个模块概念 我的理解: Git是版本管理工具,它主要对指定目录下的一些特定的文件的修改进行版本管理。 相关的模块有&#x…

Linux中点号,星号,加号,问号实战

目录Linux中的星号,点号和加号概念我的学习过程:我的思考过程:点号(.)星号(*)加号()问号(?)linux星号,点号,加号&#xf…

广播地址的计算方法(与运算、或运算)

目录我的学习过程Python中逻辑运算符notandor位运算符取反(~)与(&)或(|)广播地址计算方法IP地址子网掩码网络地址广播地址广播地址计算举例我的学习过程 今天学习UDP的单播、多播、广播中,…

Wt::WTreeNode

2019独角兽企业重金招聘Python工程师标准>>> A single node in a tree. 〔 这个 widget 渲染的是一棵树的一个节点。〕 A tree list is constructed by combining several tree node objects in a tree hierarchy, by passing the parent tree node as the last arg…

匿名内部类探究——它是一个实例

目录我的学习过程匿名内部类概述匿名内部类探究代码验证(匿名内部类是一个实例)结论我的学习过程 昨天想学习一下Java8新特性,看到Lambda表达式可以替代匿名内部类。我对匿名内部类不太理解,决定学习一下。并进行了下面的归纳和思…

Lambda表达式及应用

目录Lambda表达式概念应用在forEach()方法使用用来替代匿名内部类代码验证(Lambda表达式替代匿名内部类)Lambda表达式 概念 语法形式: () -> {} 组成: 括号:表示参数列表;箭头:表示lambda…

网络地址和广播地址的快速计算方法

目录前提条件方法原理网络地址快速计算示例广播地址快速计算示例前提条件 由IP地址和子网掩码,快速计算网络地址和广播地址。 小窍门前提:当子网掩码组成只有255和0组成时。 方法原理 利用255(或者0)和其他数字的&&#xf…

把十六进制字符转换成十进制数

2019独角兽企业重金招聘Python工程师标准>>> /*** Get the hex value of a character (base16).* param c A character between 0 and 9 or between A and F or* between a and f.* return An int between 0 and 15, or -1 if c was not a hex digit.*/public stat…

SQL Server更新某一列中多个字典码对应内容(sql示例)

目录示例-查询出多个字典码对应的内容示例-替换多个字典码对应的内容说明CHARINDEXFOR XML PATH示例-查询出多个字典码对应的内容 建立表格:学生-学习科目表student_study 注意:科目kemu列内容是字典码,需要更换成对应内容。 建立表格&…

mybatisPlus中的field-strategy(字段更新插入策略):null值插入和更新问题

目录mybatisPlus中null值插入和更新问题实际项目解决方法示例一实际项目解决方法示例二field-strategy字段更新插入策略介绍枚举类FieldStrategy源码枚举类字段简介mybatisPlus中null值插入和更新问题 配置mybatisPlus的项目中,默认进行了不是全量更新的策略&#…

linux构建主从域名服务器

实验步骤:一、构建主域名服务器1、安装域名服务(BIND服务器软件包)BIND软件包是目前Linux下使用最广泛的DNS服务器安装包,它可以运行到大多数UNIX服务器中,也包括Linux系统。RHEL4默认没有安装BIND服务器软件包&#x…

管道过滤器(Pipe-And-Filter)模式

按照《POSA(面向模式的软件架构)》里的说法,管道过滤器(Pipe-And-Filter)应该属于架构模式,因为它通常决定了一个系统的基本架构。管道过滤器和生产流水线类似,在生产流水线上,原材料在流水线上经一道一道的…

PCI_Express规范第七章解读-Software Initialization and configuration

7.Software Initialization and configuration PCI EXPRESS Configuration model 支持两种配置空间的访问机制: -PCI 兼容配置机制:100%的二进制兼容PCI 2.3中定义的,以及兼容早期的OS或类似的总线枚举和配置软件。 -PCI Express增强配置机制…

Asp.net基础概念之 事件

.NET Framework中的事件 事件体系结构 引发事件的对象叫做事件源。事件源发布它可以引发的事件。事件的类型总是委托类型,这个委托类型的签名定义了处理该事件的方法的签名。定义事件处理程序方法的对象称为事件接收方。事件接收方预订要在事件源上处理的事件事件接…

Facebook图片存储架构技术全解析

Haystack提出了一种通用的基于HTTP的对象存储,它含有指针,映射到存储对象。在Haystack中以指针储存照片,把数以十万计的图像聚集到一个Haystack存储文件,从而消除了元数据负荷。这就使得元数据的开销非常小,并且使我们…