我理解托管和非托管之间的区别,我也了解GC的工作方式,所以请不要告诉我这个。
让我感到困惑的是,当我创建和打开时,让我们说一个StreamReader类。基本上.NET会在这个包装类中完成所有工作,为我打开文件等。由于.NET完成了所有肮脏的工作,为什么它不能跟踪资源,因此一旦StreamReader对象不再生根,它就可以释放它们。换句话说,为什么我们必须实现Dispose方法并释放这些资源?为什么.NET无法为我们这样做?
答案 0 :(得分:3)
.NET没有完成所有肮脏的工作,肮脏的工作是在.NET中完成的,这并不完全相同。
现在,StreamReader
不处理非托管内存或任何其他非托管资源。它经常处理具有非托管资源的类,尽管通常是流处理而不是非托管内存。
但是,为了示例,我们说它确实如此。我们假设它有以下字段:
private IntPtr _chunkOfUnmanagedMemory;
private int _lengthOfUnmanagedMemory;
好。
...为什么不能跟踪资源...
确实如此。那个IntPtr
字段正是这样做的。
...所以它可以释放它们......
为什么要这样?实际上,我会在......之后回到这里。
...一旦StreamReader对象不再生根......
嗯,有一点我知道它不会再被生根,直到它被扫描以查看哪些物体不再生根。可以使用垃圾收集来检测最后立即释放的引用,但这会产生开销,并且必须在多线程方案中锁定它的开销(或者更糟糕的是,可能是多线程方案,几乎所有的堆使用都可能是多线程的,并且仍然需要一些处理循环引用的方法。所以这不是.NET的作用,它没有意识到对象是无根的,直到它需要更多的内存并试图在获得更多内容之前查看它是否可以清理它已有的一些内容。
所以真的必须是" ...一旦发现没有根源......"。
无论如何,回到:
...所以它可以释放它们......
为什么要释放它们?这可能意味着释放出另一个进程正在使用的大量内存。它不仅可能导致有问题的应用程序崩溃,而且还可能导致另一个通过共享内存与之通信的应用程序崩溃。有两件事比崩溃应用程序更糟糕,其中一件事就是崩溃了另一个应用程序。另一个是崩溃操作系统,这也是可能的这种方式(尽管今天大多数操作系统都会保护自己免受大多数此类情况的影响)。
所以.NET不仅要知道有一个指向非托管内存的指针(它确实如此),而且它还拥有"拥有"由对象发布,以及如何发布它(GlobalFree
上的包装,HeapFree
,LocalFree
,VirtualFree
,CoTaskMemFree
,{{ 1}},delete
或自定义内存管理器,仅列举Windows上的一些可能性,更不用说其他操作系统,并且通常不可能知道要调用哪些内容,除非知道内存是多少分配)。更糟糕的是,与free
一起使用的类实际上可能在现实生活中使用,其中它可以是网络套接字,文件句柄或其他东西的句柄。
即使我们可以以某种方式处理所有这些操作系统中所有可能类型的分配/发布资源的代码,但仍然只限于已经发明的东西,这不是未来的证据生活的限制。
有时你想要积极地关闭其他东西可能引用的东西。要获取StreamReader
示例,如果它从其他地方传递了StreamReader
,您可能会到达要关闭流的点,并强制其他引用它的代码检测它是否可以在尝试之前使用,或抛出异常,因为任何进一步的使用是一个错误:即使我们立即检测到无根,可能不会很快。
因此,在所有想法中,.NET应该毫不犹豫地发布一堆东西是没有意义的。我们需要有一个机制来说"如果正在收集这个对象(因为它已经被发现不再被生根)并且它还没有释放这个资源(因为它可能有已经手动完成)然后通过以下方式释放它"。这正是终结者所做的事情。
既然我们可能不想等到这种情况发生,或者可能想要在我们仍然有引用的情况下进行清理,或者至少可能希望避免通过复活对象进入终结队列来减慢GC的速度,我们还想让程序员明确地调用相同的清理方法。这恰恰是Stream
(在某些情况下还有Dispose()
)的作用。
我们还想要一种通过所有权链传递Close()
请求的方法,这就是为什么在现实生活中Dispose()
没有任何非托管资源,以及因此不需要终结者,它有StreamReader()
在Dispose()
上调用Dispose()
,除非Stream
为真。
最终,如果.NET完全负责某些事情并且因此知道它是否应该清理,我们就不会将其称为“#34; unmanaged&#34” ;,因为根据定义它将被管理。