字符串和垃圾回收

22

我听到了关于这个话题的不同说法,现在想寻求一些清晰明确的答案。

如何立即丢弃一个string对象,或者至少清除它的痕迹?


3
这是一个关于安全性问题还是关于有效利用内存以及垃圾回收工作原理的问题? - Ian Mercer
1
正如其他人所提到的,要避免使用GC.Collect。使用它实际上会损害性能,因为它会将本来寿命短暂的对象无谓地推进更长寿的代中。如果您将字符串声明为局部变量并让其超出范围,则会在Gen0(最频繁收集的代)中收集它们。 - Josh
1
我会说一点两者的。 - Kyle Rosendo
8个回答

11

这取决于字符串类型。默认情况下,文本字符串被池化 (interned),因此即使您的应用程序不再引用它,它也不会被垃圾回收,因为它由内部的池化结构引用。其他字符串就像任何其他托管对象一样。一旦应用程序不再引用它们,它们就有资格进行垃圾回收。

关于池化的更多信息,请参见此问题:Java和.NET字符串字面值位于哪里?


有没有办法使某些字符串字面量不被内部化? - Kyle Rosendo
2
@Kyle:只有字面字符串会自动进行内部化。因此,如果您有 string s = "hello"; 它将被内部化,而在运行时创建的任何字符串都不会被内部化,除非您自己这样做。 - Brian Rasmussen
2
Kyle: 不行。字面字符串是你的程序集的一部分,而你的程序集永远不会被垃圾回收。但是你的程序集是否真的包含了如此多的字符串字面值,以至于它们造成了内存压力?或者如果这与安全有关("清除痕迹"),请记住,用户可以检查你的程序集,包括其字面字符串,而无需执行它-实际上比检查运行时构建的字符串更容易! - itowlson
@Kyle:抱歉,看看itowlson的评论。 - Brian Rasmussen
@itowlson - 当然,这些只是威慑手段而已。所以,如果我将一个字符串加载到SecureString中,只是为了防止它被窥视,这是否足够作为一种威慑手段? - Kyle Rosendo
显示剩余6条评论

8

1
当然,一切都很好,但问题在于参数等。在它们被保护之前,它们将以纯文本形式存在,这样做就没有意义(或者非常小的意义)。内存收集方面很棒,但是代价高昂,不是吗? - Kyle Rosendo
那么,为什么不能将SecureString用作参数呢?我从未尝试过这样做的必要性,但它似乎应该可以工作并且是安全的。如果有人将需要保密的内容存储在非安全变量中,您可以肯定简单的调试器可以获取明文内容。看看一个用作密码字段的简单文本框就知道了。 - CG.
只需使用Windows API查看原始内容,世界上所有的*都是无用的。现在,如果该文本框是一个继承的用户控件,它将替换后备字符串变量为SecureString实例,并将该实例传递给安全调用函数,那么还需要做什么? - CG.
我想说的更多是互操作性的问题。如果我有一个用PHP编写的WebService,那么它将变得无用,因为SecureString需要将其作为普通字符串加载(在此不考虑加密)。 - Kyle Rosendo
SecureString 之所以有逐字节操作符,是因为这就是它的使用方式。从用户/文件/流等读取到 SecureString(逐字节),然后逐字节写入加密提供程序/流。完整字符串不应该在内存中,最坏情况下只会有很多没有顺序的单个字节。 - Basic
1
微软不再鼓励使用SecureString,因为它已经不再被认为是安全的。 - Bip901

5
我为字符串类编写了一个小的扩展方法,用于此类情况,这可能是确保字符串本身在收集之前不可读的唯一可靠方法。显然,它只适用于动态生成的字符串而非字面量。
public unsafe static void Clear(this string s)
{
  fixed(char* ptr = s)
  {
    for(int i = 0; i < s.Length; i++)
    {
      ptr[i] = '\0';
    }
  }
}

1
请注意,这不会清除在垃圾收集器压缩堆或操作系统将进程的虚拟内存页面移动到RAM时创建的任何字符串副本。 - Taedrin

3
这完全由垃圾收集器来处理。您可以通过调用 GC.Collect() 强制运行清理。从文档中可以看到:

使用此方法尝试回收所有不可访问的内存。

所有对象,无论它们在内存中存在多长时间,都将被考虑进行收集;但是,在托管代码中引用的对象不会被收集。使用此方法强制系统尝试回收最大量的可用内存。

这应该是最接近的翻译了!

6
可能值得补充的是,强制进行垃圾回收通常不是“正确”的做法……而且除非你能解释垃圾回收的工作原理和LOH是什么,否则最好不要去操作它! - Ian Mercer

3
我将从安全角度回答这个问题。
如果出于安全原因想要销毁字符串,那么可能是因为您不希望任何人窥探您的机密信息,而且您预计如果计算机被盗或受到其他威胁,则他们可能会扫描内存或在页面文件中找到它。
问题在于,一旦在托管应用程序中创建了 System.String,您实际上没有太多可做的事情。可能有一些狡猾的方法可以通过不安全的反射并覆盖字节来达到目的,但我无法想象这样的方法会可靠。
诀窍是根本不要将信息放入字符串中。
我曾经在为某个公司的笔记本电脑开发的系统中遇到过这个问题。硬盘没有加密,我知道如果有人拿走了笔记本电脑,他们可以很容易地扫描其中的敏感信息。我想保护密码免受此类攻击。
我处理它的方式是:通过捕获文本框控件上的按键事件将密码放入字节数组中。文本框永远不包含除星号和单个字符以外的任何内容。密码从未以任何形式存在于字符串中。然后,我对字节数组进行了哈希处理并将原始值归零。哈希然后与随机硬编码密钥进行异或,并用于加密所有敏感数据。
在加密完成之后,密钥被清零。
当然,某些数据可能以明文形式存在于页面文件中,也可能会检查最终密钥。但该死的,没有人会偷密码的!

干得好,不过如果信息通过 Web 服务以字符串形式传递(当然全部加密),你会怎么做呢? - Kyle Rosendo
@Kyle Rozendo - 如果加密对应用程序是透明的(例如SSL),那么你可能无能为力,但如果你自己进行加密(例如使用System.Security.Cryptography命名空间),那么所有操作都是以字节数组完成的,因此仍然不需要生成字符串。当然,一旦你向用户展示了它,那么一切都无法保证。 - Jeffrey L Whitledge
你的技巧很聪明,但我认为任何键盘记录器都可以轻松绕过它,通过存储键盘上的所有按键。 - Roger Hill
@MadTigger - 当然,或者有人可以将一个小摄像头安装在天花板上,记录下所有的按键操作等等。幸运的是,这些笔记本电脑并没有保存国家安全机密之类的东西。我的主要担忧是在笔记本电脑被盗后的数据盗窃,而我认为我已经完成了90%的工作,这在这种情况下可能已经足够了。 - Jeffrey L Whitledge

2

没有确定的方法可以清除内存中字符串(System.String)的所有痕迹。您唯一的选择是使用SecureString对象。


1
在内存中,也没有确定性的方法可以清除字符数组的所有痕迹,是吗? - itowlson
1
我认为有办法,只需要将数组的所有元素值设置为0即可。 - Aviad P.
当然,没有确定性的方法来清除字符数组的所有痕迹,但如果您使用该字符数组来表示一个字符串,则可以使用字符数组并将其所有元素设置为0来确定性地清除内存中该字符串的所有痕迹。 - Aviad P.
1
但我想你是对的,如果该数组在某个时刻已被垃圾收集器移动过,那么一个旧版本(具有非0值)可能会留在内存中的某个地方。 - Aviad P.
您可以使用fixed语句来防止垃圾回收器移动数组。 - Wai Ha Lee

1

限制内存中字符串对象的生命周期的最佳方法之一是将它们声明为尽可能内层作用域的本地变量,而不是在类上声明为私有成员变量。

初级开发人员常见的错误是在类本身上将他们的字符串声明为'private string ...'。

我也看到过有好心的有经验的开发人员试图在私有成员变量中缓存一些复杂的字符串连接(a+b+c+d...),以便他们不必一直重新计算。大错特错——重新计算几乎不需要时间,在第一代GC发生时临时字符串会立即被垃圾收集,而缓存所有这些字符串所占用的内存只是从更重要的项目(如缓存的数据库记录或缓存的页面输出)中占用可用内存。


1
如果字符串很大,并且连接操作频繁进行,那么缓存是个好主意。快速拼接并且很快被丢弃的大型字符串会导致大对象堆的碎片化,而这在垃圾回收期间是不会被压缩的。当然,在这些情况下使用StringBuilder等工具也是有用的,可以减少对堆的影响。 - Niall Connaughton

-5

一旦不再需要字符串变量,请将其设置为null。

string s = "dispose me!";
...
...
s = null;

然后调用 GC.Collect() 来请求垃圾回收器,但是 GC 无法保证字符串会立即被回收。


4
这是一个不幸的例子:根据Brian的回答,因为dispose me!是汇编代码中的文字,它将被内部化并永远不会被垃圾回收。然而就运行时构建的字符串而言,你是正确的。 - itowlson
-1 对于任何建议使用GC.Collect来“处理”字符串的人。但是itowlson关于内部化的观点是正确的。 - Josh
哦,这真让我惊讶!谢谢。 - Ricky
即使进行垃圾回收,也并不意味着数据不仍然存储在内存中,如果有人冻结了你的内存并将其转储... - cb88

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