【问题标题】:How does c# remove the memory allocated in loop?c#如何删除循环分配的内存?
【发布时间】:2013-04-06 09:50:05
【问题描述】:

想象一下这段代码:

int i=9999999;
while ( i > 1 )
{
    string UnusedMemory="this is a string that eats some ram" + i.ToString();
    i--;
}

如果仅在运行GC.Collect() 时才删除未引用的对象,则此代码应分配大量内存,直到发生收集。但它根本没有分配巨大的内存,为什么?是否在 IL 级别实施了某种“删除”?还是GC.Collect() 自动调用更快?我知道这是一个微不足道的例子,但如果它更复杂并且在该代码块中访问字符串,它无论如何也不会吃掉很多内存。

编辑:我更改了示例,使字符串始终是唯一的,因此它不能被“缓存”

【问题讨论】:

  • 因为 UnusedMemory 变量是在循环内定义的,所以它只对每次迭代有效。每次迭代后 UnusedMemory 都是 GCd ......我相信,它不会保留它
  • @craig1231 所以你认为每次迭代后都会调用 GC?那个CPU不是很贵吗?
  • @Petr GC 不会在每次迭代中调用。 UnusedMemory 被丢弃(实际上可能不是因为它可以被重用),但是字符串本身是一个引用类型,所以它只是在没有任何引用的情况下浮动,并且在 GC 启动时的某个时候被收集。

标签: c#


【解决方案1】:

字符串常量被“缓存”在字符串池中,所以每次都是同一个字符串。

如果你对字符串使用String.IsInterned方法,那么它将返回true,这意味着它包含在池中。

来自String.IsInterned

公共语言运行时自动维护一个表,称为 实习生池,其中包含每个唯一的单个实例 程序中声明的文字字符串常量,以及任何唯一的 您以编程方式添加的 String 实例。

实习生池保存字符串存储。如果你分配一个文字 字符串常量到几个变量,每个变量都设置为 在实习生池中引用相同的常量而不是引用 具有相同值的多个不同 String 实例。

此外,当需要内存或分配了足够的元素时,会自动调用 GC,因此即使不缓存字符串也不会成为问题。

来自Fundamentals of Garbage Collection

当下列条件之一为真时发生垃圾收集:

  • 系统物理内存不足。
  • 托管堆上分配的对象使用的内存超过了可接受的阈值。这意味着已超出托管堆上可接受的内存使用量的阈值。此阈值会随着流程的运行不断调整。
  • GC.Collect 方法被调用。在几乎所有情况下,您都不必调用此方法,因为垃圾收集器会持续运行。此方法主要用于特殊情况和测试。

是否可以更改或优化此行为? 是的,在某种程度上:Latency Modes

这个问题与以下内容非常相关: C# memory usage for creating objects in a for loop

【讨论】:

  • 好的,你能详细说明一下“如果有足够的元素”,多少元素就足够了? GC 如何知道何时应该或不应该删除它们?是否有可能改变或优化这种行为?
  • @Petr 我认为您无法获得关于何时足够的确切答案,但您可以使用我添加的链接优化行为。 “GC 怎么知道什么时候应该或不应该删除它们?”此处解释了基础知识:msdn.microsoft.com/en-us/library/ee787088.aspx 在“发生了什么...”部分中,如果您想了解更多详细信息,请查看cs.au.dk/~mis/dOvs/slides/45a-garbagecollection.pdf
【解决方案2】:

当一个对象从堆中被分配,并且没有足够的可用空间时,垃圾收集器将运行。如果第一个堆的集合没有释放足够的内存,它将继续在下一级堆上进行集合。只有当所有堆的集合没有释放足够的内存时,它才会从系统中分配更多的内存。

由于您的示例中有很多最近释放的对象,因此第一个堆的集合将始终释放足够的内存。您的循环将多次填充堆。你可以在循环前后打印出GC.CollectionCount(0),看看它运行了多少次。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-19
    • 2016-09-05
    • 2017-05-03
    • 1970-01-01
    相关资源
    最近更新 更多