首页 文章

关于.net中内存管理的疑虑

提问于
浏览
5

我正在从“Professional C#”一书中学习C#中的内存管理

垃圾收集器的存在意味着您通常不会担心不再需要的物体;您将简单地允许对这些对象的所有引用超出范围,并允许垃圾收集器根据需要释放内存 . 但是,垃圾收集器不知道如何释放非托管资源(例如文件句柄,网络连接和数据库连接) . 当托管类封装对非托管资源的直接或间接引用时,您需要进行特殊配置以确保在对类的实例进行垃圾回收时释放非托管资源 . 定义类时,可以使用两种机制自动释放非托管资源 . 将析构函数(或终结器)声明为您的类的成员 . 在类中实现System.IDisposable接口 .

我不明白一些事情:

  • “非托管资源(例如文件句柄,网络连接和数据库连接)” . 关于他们的重大事项是什么?为什么他们不受管理? (或)为什么GC不能管理这些资源?

  • 我们将在一个类的终结器或Dispose()方法中放置什么代码以及该代码究竟是什么样的?使用这些资源的一些例子会有很多帮助 .

8 回答

  • 2

    这里真正的问题是紧迫性 . 当垃圾收集器明确跟踪内存时,它将知道何时需要通过清除未引用的对象来释放内存 . 这可能每分钟发生几次,或者每小时发生一次,甚至从不发生(如果不需要创建新对象) . 但重要的是它确实在需要时发生 .

    但记忆并不是唯一有限的资源 . 拿文件 . 通常,一次只有一个应用程序可以打开文件,因为如果有几个人试图写入同一个文件,它可能会变得混乱 . 数据库的连接数量有限 . 等等 . 垃圾收集器不跟踪任何这些资源 . 并且它不知道关闭它们有多紧急 .

    当然,您可以打开一个FileStream并从中读取,而不会在之后关闭它 . 如果你取消对对象的引用,最终垃圾收集器可能会决定收集FileStream对象,它将运行Finalizer并且文件将被正确关闭 . 但这可能需要很长时间,同时文件被锁定 .

    对于数据库连接,它更加紧迫,因为可用的集合数量非常有限,因此如果打开太多连接而不处理它们,最终会出现错误,因为您将拥有一堆具有开放连接的数据库对象躺在垃圾收集器队列中等待 .

    因此,正确处理一次性物品是一种很好的做法 . 有时你可以逃避不这样做,但它的风格很差 . 如果一个对象实现了IDisposable,那是因为它要你在使用它时清理它 .

  • 6

    .NET框架上的某些类只是Windows API或第三方程序集的包装 . 这些API不是托管代码(它们可以用C编写,或者它们是旧的COM程序集),垃圾收集器不知道应用程序何时不再需要它们 .

    例如,当您打开磁盘文件时,它将保持打开状态,直到您告诉它关闭该文件 . 如果在不关闭文件的情况下销毁指向文件的指针(即离开作用域),则此文件将保持打开并锁定 .

    在Framework上为这些类实现的Dispose方法调用以干净的方式完成实例所需的内部Close方法 . 所以包装非托管代码的所有类都应该实现Disposable接口,以确保它实现的关闭方法 .

    然后,当您使用using语句对该类进行实例化时,这是一个很好的做法,因为当您离开作用域时,会自动调用Dispose方法 .

  • 3

    1.)GC不知道如何正确关闭外部资源 . 当然,他可以杀死一个网络连接(事实上,如果你不断开连接,那就是他所做的事情) . 但是没有通知数据库关闭连接 .

    类似的是文件流 . 缓冲区中还有什么东西吗?在关闭文件句柄之前是否必须将其写入文件? GC不知道这一点 - 访问代码呢 .

    2.)接下来是什么 . 因此,如果您有打开的文件流和内部缓冲区 - 在dispose方法中,您将刷新缓冲区,将其写入文件并关闭文件hanlde .

    通常,您不直接访问数据库 . 您可以使用库来管理它 .

    在大多数情况下,如果您的 class 正在处理,那么就可以配置那些外部资源管理器(Db连接,文件流,网络类) .

  • 1

    这是一个很好的问题,很多开发人员似乎都不理解 .

    在较高级别,托管资源是由.Net分配和跟踪的资源 . 资源使用的内存来自分配给.Net的池,而.Net运行时跟踪托管资源之间的所有引用 . 这种跟踪(我确定这是错误的术语,但在这里就足够了)允许.Net运行时知道何时不再使用给定资源并因此有资格被释放 . 因此,非托管资源是在.Net托管池之外分配的资源,而不是由运行时跟踪的资源 . 通常,这些是对OS或外部应用程序资源的引用 . .Net运行时无法“看到”非托管资源有各种复杂的原因,但我喜欢这样想:.Net是一个有围墙的开发园区,您必须输入才能使用 . 您可以在该墙上戳一个洞以查看外部(即PInvoke),但您不能在另一侧拥有资源 .

    现在,问题的第二部分 . Bill Wagner在如何实现Dispose方法以及为什么在他的书_1247338中进行了很好的讨论 . 关于这个herehere也有一些非常好的答案 .

    希望这可以帮助 .

  • 0
  • 1

    非托管资源是操作系统拥有和控制的资源的句柄(当然不是内存) .

    GC不会立即清除内存,因为不再有任何对象的引用 - 它可能会长时间保留 . 如果它使用文件,网络和图形处理这样做,它可能会占用大量的操作资源,并且只会偶尔发布它们 .

    为了将这些非托管资源释放回操作系统,您需要通过处理它们来明确释放它们 . 因此使用IDisposable和using关键字 .

  • 1

    我不喜欢引用文本使用术语“非托管资源”的方式,因为它表明该术语主要指操作系统知道的对象 . 事实上,我认为将“非托管资源”视为当前对象之外的东西(可能在计算机之外!)会更有帮助,其使用寿命可能超过当前对象的状态,其状态可能已被更改如果不清理会导致问题的方式,以及预期当前对象清理的方式 . “托管资源”是对包含一个或多个“非托管资源”的对象的引用,但是即使它被放弃,它通常也会设法处理这些资源(至少最终) .

    即使在完全托管的代码中,也可以拥有非托管资源 . 举个简单的例子,集合的枚举器可能会订阅一个事件,因此如果集合发生变化,它会收到通知 . 集合的事件订阅列表是非托管资源 . 除非调查员在放弃之前取消订阅事件,否则持有事件订阅的集合的使用寿命可能会超过调查员的使用寿命 . 虽然偶尔放弃的事件订阅可能不会造成太大的伤害,但是创建许多枚举器并在不清除订阅的情况下放弃它们的例程可能会造成严重破坏 .

  • 11

    我在实践中已经完成了很多编码 - 本机代码 - C - 也称为非托管和托管代码 - C# . 我还不确定为什么C#首先被发明 - 当然开发人员有很多改进,但C#架构背后隐藏着很多隐藏的东西 .

    据我所知,很多微软专业开发人员都接受了让C#发生的任务,就像通常在任何新平台上一样 - 开发人员对他们的技术过度兴奋 .

    第一次尝试当然是声称“我们正在做正确的事情”,而其他人都做错了 - 我想因为这个而出现了“非管理”一词 . 这就像我们在这里“管理”代码和设计不正确的东西 - “UN” -something . :-)

    文件句柄,网络连接,数据库连接等非托管资源已经管理了很长时间 - 如果终止进程,它将关闭所有文件句柄 .

    在C你有malloc,免费,在C#你有新的(或gcnew),但是你正在与什么引用什么,为什么这个对象不会远离记忆,什么是吃ram的问题 - 以及大多数这些问题的答案很难回答 .

    构造函数/析构函数被终结器,析构函数,一次性对象替换,测试调用的内容相对困难,按顺序排列,还记得释放所有资源吗? “哦,我们是管理的,但我们不知道如何管理这些对象......”:)

    C#很有趣,只要它是小而简单的应用程序 - 在你添加3d对象之后,超大量的分配,很多功能,编译开始变慢,你不再对C#感到满意并且在考虑回到C .

    在很多论坛上你可能会发现一些争论 - 是C#还是C更好/更快/更容易 - 而且大多数人都试图不惜一切代价保护C# . 现实是,它过于复杂,过度抽象,太重的怪物,没有人可以控制它 .

    中间语言(IL) - 代表源代码和可执行代码(汇编)之间的一个额外抽象层,这使得优化和提升程序更加困难 .

    但我不是C的忠实粉丝 - 语言复杂性与指针,引用和对象/类本身并不能使C更容易编码或更容易学习 .

    基本上,当您构建新语言时 - 您需要考虑目标语言基础结构(低级程序集与C代码的平滑集成和高级代码改进 - 易于使用,易于理解,易于开发,易于维护和改进) .

    在理论上创造更多的“单词”可能会使语言变得更丰富 - 你可以用更少的文本和更有效的方式表达自己,但这又不能防止语言本身的污染(如IDisposable) .

    我故意将编程语言与自然语言进行比较,因为我现在正在用自然语言编写答案,而且它比编程语言更像是我原生的 . 然而,编程语言的结构比自然更好,并且没有2016年的历史 .

    2 - 终结者/处置 - 已阅读本章至少5次,但仍然不理解 . 我通常创建一个函数(关闭)并从两个函数调用它 - 从终结器 - 和从dispose . 为什么要费心去理解一些不重要的东西 .

    无论如何,我对你的推荐 - 尝试代码中的所有内容 - 它的外观,感觉如何 . 书籍往往会变成类似于圣经的东西 - 它们会把你拖入宗教,你不一定想要这样 .

相关问题