在 using 块内返回可丢弃对象

3

我注意到在分析我的程序时,有很多byte[]被卡在内存中。经过一番调查,我发现其中大部分实例是通过以下方式创建的:

public byte[] CreateBytes(byte[] bytes)
{
    using (var start = new MemoryStream()) 
    {
        using (var memStr = new MemoryStream(bytes))
        {
            //do stuff
            return start.ToArray();
        }
    }
}

返回的 byte[] 然后传递给其他方法,并在另一个 using 块内创建另一个 MemoryStream:
using (var uncompressedStream = new MemoryStream(uncompressedData))
{
    using (var compressedStream = new MemoryStream())
    {
        //Do some compression
    }
}

myObject.Bytes = uncompressedData;
uncompressedData = null;

return myObject;

(uncompressedData是从CreateBytes()函数返回的值。)
我的问题是,byte[]数组何时被清理?我需要明确地将它设置为null吗?如果需要,应该在哪里设置?在第二个using块之后,我不再需要它了,但如果我只是放置uncompressedData = null;,我不确定这会释放内存。
我本以为,在CreateBytes(byte[] bytes)的using语句中,会处理这些字节的处理,但既然它返回一个引用,那么是否推迟和/或放弃处理呢? 编辑:我添加了另一行代码。因为我正在将未压缩的字节存储在另一个对象中,所以将uncompressedData设置为null是没有意义的,byte[]数组将与myObject一样长时间存在(或直到myObject.Bytes设置为null),对吗?

1
在您的代码中,using 只会处理非托管对象,例如 MemoryStream。而 bytes 则由垃圾回收进程(非确定性)管理和清理。 - hIpPy
4个回答

5

只有两个条件都满足时,byte[] 才会被清理:

  • 没有任何引用指向该内存块
  • 垃圾回收器决定收集该内存块

垃圾回收器的运行时间是基于多种因素的不确定性。


循环引用如果没有通过根引用引用,也将被清除。这只是一个琐碎的细节。 :) - hIpPy

1
字节数组和内存中的任何其他托管对象一样,只要它不再从任何根引用可访问,就有资格进行垃圾回收。您可以信任GC知道何时发生了这种情况,并智能地安排时间来实际清理有资格的对象。尽量不要担心有资格进行清理的对象实际上何时被清理,很可能GC比您更懂得处理。

我不担心那个,让我担心的是它没有被清理,因为它显然不符合垃圾回收的条件(因为它可能在某个地方被引用)。 - Devin
@Devin,我们没有看到足够的代码来指出你在不需要数组后引用它的地方,如果这确实是情况的话。可能只是垃圾回收器还没有清理它,或者你实际上需要保留数据那么长时间。 - Servy
我添加了一些代码,应该基本上演示了我的代码中正在发生的事情。 - Devin
@Devin 不完全是这样,或者至少不够。你只是展示了将数组分配给另一个变量,但你没有展示足够的代码来知道那个变量的生命周期。 - Servy

0
除了Eric的回答之外:
当你确定不再需要那个byte[]时,将其赋值为null
这并不能保证内存会立即被回收,也不能保证它会在之后被回收,这只是一种帮助GC识别它作为收集对象的方法。

在几乎所有情况下,当你不再需要某个东西时,那是因为它即将超出作用域,所以没有必要将其设置为 null。如果这不是真的,那么你的变量可能被作用域限制得太高了。我几乎从来没有必要将变量设置为 null,以便它可以被收集。 - Servy
@Servy:我同意这个观点,但是正如我所提到的,在复杂的交互中,这是基本的好规则指南,可能你正在处理许多嵌套调用,对于你不关心(或无法关心)的函数和类型。如果在工作流程中有一些点,你确定不再需要那些东西,就将其分配为null。总的来说,在大多数常见情况下,我会同意你的观点。 - Tigran
实际上,当您有许多复杂的嵌套调用时,正是您不需要将变量设置为null的情况,因为它们往往被正确地作用域化。您通常会看到将变量设置为null的情况是在执行数百/千行代码方法并在同一作用域内执行几十个完全独立操作时。由于每个操作没有不同的作用域,因此在开始时创建的局部变量将始终存在。有些人选择将变量设置为null,以便可以释放它们,而不是将代码重构为更多的方法。 - Servy
@Servy 我也同意将项设置为null通常是不必要的。然而,在上面的情况下(如果我理解正确),uncompressedData将会超出作用域,但我认为它只是指向在作用域之外创建的byte[],因此uncompressedData将不再指向该byte[],但它仍然存在于内存中(而我不知道在哪里,这就是问题所在)。 - Devin
这是一个不好的指南。将长寿对象中的字段设置为null是有意义的,因为它在长寿对象中。实际上将本地变量赋值为null只会让它多活几个纳秒 - 假设编译器不会将其作为无用代码编译掉 - 因为需要一些时间才能开始使用寄存器或堆栈空间进行其他操作。 - Jon Hanna
显示剩余3条评论

0

using语句与您返回的byte数组实际上没有任何关系。

MemoryStream是可释放的对象,而该对象的处理正是由using语句来管理的(请注意,处理和垃圾回收不是同一回事,但通常是其前序)。

当您调用ToArray()时,您正在创建一些新的不可释放的对象;一旦它超出范围,垃圾回收器将在不确定的时间内清理它(这通常是一个良好优化的过程)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接