.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,一经查实,立即删除!

相关文章

关于三极管偏置电路的思考

最近在做十年前应该做的事情,从最基本的模拟电路实验开始,了解电子的基本概念。还好,对事物的理解,随着阅历增加,理解的程度也不一样。从三极管偏置电路,我想到了人的自我修养和调整。当电路调整到一个合适…

搜索引擎指令站长常用搜索引擎命令汇总

身为一个网站管理员用好各大搜索引擎一些特殊指令,是最基本的网站SEO。逅客百度Google取经看到有前人整理的几个搜索引擎常用指令,单独使用是最基本,能综合使用就会体验搜索的另类魅力。以下搜索引擎指令都以学海网(www.xuehai.net)为例。 一…

Socket源码相关——SocketAddress和InetSocketAddress

目录我的学习过程我的心路历程思考总结我的学习过程 昨天学习qiujuer老师的《Socket网络编程进阶与实战》实战课程中,写了一个简易的client-server聊天项目。我的学习方法是根据课程的一部分思路提示后,自己独自进行编写,出现了很多问题&…

三轮哥

灰太狼发现自从有了犀利哥开始,什么什么哥越来越流行了,就跟当初的各种“门”一样,这不,网上盛传许久的三轮哥,灰太狼今天才有幸看到。 不过话说回来,类似三轮哥这样的人物还是少出一点的好,这玩…

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

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

FTP服务器架设详细图解

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

gradle下bug修正后问题仍存在解决思路

目录我的学习过程我的学习心路热加载配置bug问题总结我的学习过程 前天写的client-server聊天项目写完后,今天进行了调试。我用到的是out目录下的server.class文件和client.class文件。 先后启动两个命令行窗口来进行测试的。 使用java server启动服务端窗口。 再使…

IP-tools

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

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

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

数字示波器的激烈竞争

计算机、通信以及消费类电子产业的快速发展成为示波器发展的不竭动力&#xff1b;厂商不断从技术上对示波器进行改进更使其发展日新月异。 <?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />数字示波器自它诞生的第一天起&#xf…

git pull和push整理和归纳

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

重装vcenter后恢复原来制作的模板!

重新安装vcenter后发现原来用vcenter创建的模板没有了。清单中只显示现有的4台虚拟机&#xff0c;没有显示模板。其实找回来也很简单&#xff01;在清单中找到数据存储&#xff0c;在相应的模板文件夹中找到的.vmtx文件添加到清单中去即可&#xff01;转载于:https://blog.51ct…

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

目录Linux中的星号&#xff0c;点号和加号概念我的学习过程&#xff1a;我的思考过程&#xff1a;点号&#xff08;.&#xff09;星号&#xff08;*&#xff09;加号&#xff08;&#xff09;问号&#xff08;?&#xff09;linux星号&#xff0c;点号&#xff0c;加号&#xf…

如何调整HOOK的跳转指令

可以按这样的方式来存放 长度A 长度A 用于调整Short JMP 用于存放一些信息 |调整后的原HOOK代码 |原始代码(HOOK) |临时LONG JMP区| 信息区| 1). 调整…

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

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

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…

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

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

利用SQL查找表中的质数(prime number)和完全数(perfect number)以及几个有趣的SQL语句...

之前在某次interview中被老外问到如何用SQL找出列上的质数和完全数的问题&#xff1b;我当时已经多年没有写过这种考算法和SQL技巧(纯粹的技巧)的语句了&#xff0c;乍遇此问题倒是有些棘手。现在录以记之&#xff0c;供人参考. SQL> create table numbers(NO int) ;表已创建…

Lambda表达式及应用

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