我正在从“专业C#”一书中学习C#中的内存管理
垃圾收集器的存在 意味着你通常不会担心 关于你不再需要的物品; 你只需要允许所有参考 那些超出范围的对象 并允许垃圾收集器 根据需要释放内存。然而 垃圾收集器不知道如何 free 非托管资源(例如文件 处理,网络连接和 数据库连接)。管理时 类直接或间接封装 您可以参考非托管资源 需要做出特殊规定 确保非托管资源 当一个实例发布时 类被垃圾收集。
定义类时,可以使用 自动化释放的两种机制 非托管资源。
- 将析构函数(或终结器)声明为您班级的成员。
- 在您的网站中实现System.IDisposable接口 类。
醇>
我不明白一些事情:
“非托管资源(例如文件句柄,网络连接和数据库连接)”。关于他们的重大事项是什么?为什么他们不受管理? (或)为什么GC不能管理这些资源?
我们将在一个类的终结器或Dispose()方法中放置什么代码以及该代码究竟是什么样的?使用这些资源的一些例子会有很多帮助。
答案 0 :(得分:11)
.NET框架上的某些类只是Windows API或第三方程序集的包装。这些API不是托管代码(它们可以用C ++编写,也可以是旧的COM程序集),垃圾收集器不知道应用程序何时不再需要它们。
例如,当您打开磁盘文件时,它将保持打开状态,直到您告诉它关闭该文件。如果在不关闭文件的情况下销毁指向文件的指针(即离开范围),该文件将保持打开并锁定。
在Framework上为这些类实现的Dispose方法调用以干净的方式完成实例所需的内部Close方法。所以包装非托管代码的所有类都应该实现Disposable接口,以确保它实现的关闭方法。
然后,当您在实例化该类时,最好使用using语句,因为当您离开范围时,会自动调用Dispose方法。
答案 1 :(得分:6)
这里真正的问题是紧迫性。当垃圾收集器明确跟踪内存时,它将知道何时需要通过清除未引用的对象来释放内存。这可能每分钟发生几次,或者每小时发生一次,甚至从不发生(如果不需要创建新对象)。但重要的是它确实在需要时发生。
但内存不是唯一有限的资源。拿文件。通常,一次只有一个应用程序可以打开文件,因为如果有几个人试图写入同一个文件,它可能会变得混乱。数据库的连接数量有限。等等。垃圾收集器不跟踪任何这些资源。并且它不知道关闭它们有多紧急。
当然,您可以打开FileStream并从中读取,而不会在之后关闭它。如果你取消对对象的引用,最终垃圾收集器可能会决定收集FileStream对象,它将运行Finalizer并且文件将被正确关闭。但这可能需要很长时间,同时文件被锁定。
使用数据库连接更加紧迫,因为可用的集合数量非常有限,因此如果打开太多连接而不处理它们,最终会出现错误,因为您将拥有一堆数据库对象打开等待垃圾收集器队列的连接。
正确处理一次性物品是一种很好的做法。有时候你可以逃避不这样做,但风格很差。如果一个对象实现了IDisposable,那是因为它要你在使用它时清理它。
答案 2 :(得分:3)
1。)GC不知道如何正确关闭外部资源。当然,他可以杀死一个网络连接(事实上,如果你不断开连接,那就是他所做的事情)。但是没有通知数据库关闭连接。
类似于文件流。缓冲区中还有什么东西吗?在关闭文件句柄之前是否必须将其写入文件? GC不知道这一点 - 访问代码确实如此。
2。)接下来是什么。 因此,如果您有打开的文件流和内部缓冲区 - 在dispose方法中,您将刷新缓冲区,将其写入文件并关闭文件hanlde。
通常,您不直接访问数据库。您可以使用库来管理它。
在大多数情况下,如果您的班级正在处理,那么处置这些外部资源管理器(Db连接,文件流,网络类)就足够了。
答案 3 :(得分:2)
这是一个很好的问题,很多开发人员似乎都不理解。
在较高级别,托管资源是.Net分配和跟踪的资源。资源使用的内存来自分配给.Net的池,而.Net运行时跟踪托管资源之间的所有引用。这种跟踪(我确定这是错误的术语,但在此处就足够了)允许.Net运行时知道何时不再使用给定资源并因此有资格被释放。因此,非托管资源是在.Net托管池之外分配的资源,而不是由运行时跟踪的资源。通常,这些是对OS或外部应用程序资源的引用。 .Net运行时无法“看到”非托管资源有各种复杂的原因,但我喜欢这样想:.Net是一个有围墙的开发园区,您必须输入才能使用。您可以在该墙上戳一个洞以查看外部(即PInvoke)但您不能拥有另一侧的资源。
现在,问题的第二部分。 Bill Wagner在他的书Effective C#中讨论了如何实现Dispose方法及其原因。关于此here和here,还有一些非常好的答案。
希望这有帮助。
答案 4 :(得分:1)
答案 5 :(得分:1)
非托管资源是操作系统拥有和控制的资源的句柄(当然不是内存)。
GC不会立即清除内存,因为不再有对象的引用 - 它可能会长时间保留。如果它使用文件,网络和图形处理它,它可能会占用大量的操作资源,并且只会偶尔发布它们。
为了将这些非托管资源释放回操作系统,您需要通过处理它们来显式释放它们。因此使用IDisposable和using关键字。
答案 6 :(得分:1)
我不喜欢引用文本使用术语“非托管资源”的方式,因为它表明该术语主要指操作系统知道的对象。事实上,我认为将“非托管资源”视为当前对象之外的东西(可能在计算机之外!)会更有帮助,其使用寿命可能超过当前对象的状态,其状态可能已被更改如果不清理会导致问题的方式,以及预期当前对象清理的方式。 “托管资源”是对包含一个或多个“非托管资源”的对象的引用,但即使它被放弃,它通常也会设法处理这些资源(至少最终)。
即使在完全托管代码中也可以拥有非托管资源。举个简单的例子,集合的枚举器可能会订阅一个事件,因此如果集合发生变化,它会收到通知。集合的事件订阅列表是非托管资源。除非调查员在放弃之前取消订阅事件,否则持有事件订阅的集合的使用寿命可能会超过调查员的使用寿命。虽然偶尔放弃的事件订阅可能不会造成太大的伤害,但是创建许多枚举器并在不清除订阅的情况下放弃它们的例程可能会造成严重破坏。
答案 7 :(得分:0)
我在实践中已经完成了很多编码 - 本机代码 - C ++ - 也称为非托管和托管代码 - C#。我还不确定为什么C#首先被发明 - 当然开发人员有很多改进,但C#架构背后隐藏着许多隐藏的岩石。
据我所知,许多Microsoft专业开发人员接受了使C#发生的任务,并且通常与任何新平台一样 - 开发人员对他们的技术过度兴奋。
第一次尝试当然是声称“我们做的是正确的事情”,而其他人都做错了 - 我想因为这个而出现了“非管理”一词。这就像我们在这里“管理”代码和设计不正确的东西 - “un” - 一些东西。 : - )
文件句柄,网络连接,数据库连接等非托管资源已经管理了很长时间 - 如果终止进程,它将关闭所有文件句柄。
在C ++上你有malloc,免费,在C#你有新的(或gcnew),但是你正在解决什么引用什么的问题,为什么这个对象不会从记忆中消失,什么是吃ram - 这些问题的大部分答案都很难回答。
构造函数/析构函数被终结器,析构函数,一次性对象替换,测试调用的内容相对困难,按顺序排列,还记得释放所有资源吗? “哦,我们是管理的,但我们不知道如何管理这些对象......”:)
只要它是一个小而简单的应用程序,C#很有趣 - 在你添加3d对象之后,超大量的分配,很多功能,编译开始变慢,你不再对C#感到满意了并考虑回到C ++。在很多论坛上你可能会发现一些争论 - 是C#还是C ++更好/更快/更容易 - 而且大多数人都试图不惜一切代价保护C#。现实是它过于复杂,过于抽象,太重的怪物,没有人可以控制它。
中间语言(IL) - 代表源代码和可执行代码(程序集)之间的另一个抽象层,这使得优化和提升程序变得更加困难。
但我不是C ++的忠实粉丝 - 带指针的语言复杂性,引用和对象/类本身并不能使C ++更容易编码或更容易学习。
基本上,当您构建新语言时 - 您需要考虑目标语言基础结构(低级汇编+与C ++代码的平滑集成和高级代码改进 - 易于使用,易于理解,易于开发,易于维护,并改进)。
理论上创造更多“单词”可能会使语言变得更丰富 - 你可以用更少的文本表达自己,并且更有效率,但这又不能防止语言本身的污染(如IDisposable)。
我故意将编程语言与自然语言进行比较,因为我现在用自然语言编写答案,而且它比编程语言更像是我的原生语言。然而,编程语言的结构比自然更好,并且没有2016年的历史。
2 - 终结者/处置 - 已阅读本章至少5次,但仍然不理解。我通常创建一个函数(关闭)并从两个函数调用它 - 从终结器 - 和从dispose。为什么要费心去理解一些不重要的东西。
无论如何,我给你的建议是 - 尝试代码中的所有内容 - 它的外观,感觉如何。书籍往往会变成类似于圣经的东西 - 它们会把你拖入宗教,你不一定要这样做。