C#内存使用过多

3

我有一个包含多个步骤的进程,它们被定义为一个通用策略模式的实现。在这个过程中,所有步骤都会传递一个共同的ProcessParameter对象。(对其进行读/写操作)

这个ProcessParameter对象具有许多数组和集合。例如:

class ProcessParameter() {
   public List<int> NumbersAllStepsNeed {get; set;}
   public List<int> OhterNumbersAllStepsNeed {get; set;}
   public List<double> SomeOtherData {get; set;}
   public List<string> NeedThisToo {get; set;}
   ...
}

步骤完成后,我想确保内存已被释放且不残留,因为这可能会占用大量内存,并且其他进程也需要运行。

我是通过运行以下命令来实现的:

pParams.NumbersAllStepsNeed = null;
pParams.OhterNumbersAllStepsNeed = null;
pParams.SomeOtherData = null;
...

或是ProcessParameter应该实现IDisposable接口,并使用Dispose方法进行处理,然后我只需要使用()(或将其包装在using块中)

什么是清除运行中一个进程使用数据的内存占用量的最佳和最优雅的方式?
使用数组而不是列表会有所改变吗?或者混合使用呢?我需要的实际参数类型是自定义对象的集合/数组。
我是否朝着正确的方向寻找答案?
更新
很棒的问题!感谢您的评论!
我曾经将这个进程作为单个运行,我可以看到内存使用量非常高,然后逐渐降至“正常水平”。
问题出在我开始将这些进程链接在一起并使用不同的起始参数时。那时内存飙升得非常高,因此我想在两个进程之间加入清理步骤,并寻找最佳方式来完成这项工作。
有一个数据库,这个参数是一种“缓存”,以加快速度。
IDisposable的观点很好,我没有在params对象中保留不受管控的资源。

2
你是在尝试解决实际问题还是在问如何实现过早优化? - Dan Wilson
3
IDisposable 用于释放非托管资源。要使托管对象有资格进行垃圾回收,只需要确保没有对它的引用即可。参见 What is the correct way to free memory in C# - Igor
2
一旦ProcessParameter不再可达并被收集,其属性中包含的所有对象也将被回收。通常情况下,除非您保留ProcessParameter实例的时间比子对象长得多,否则不需要显式地将它们设置为null。如果确实有这样的需求,您应该认真考虑在诉诸半手动内存管理花招之前先拆分对象。首先,这些东西是等待发生NullReferenceException意外的事情。 - Jeroen Mostert
2
如果没有涉及到非托管资源,那么你可能需要使用GC.Collect和GC.WaitForPendingFinalizers,而不是Dispose。然而,在大多数情况下,这样的hack是无用的... - Alex F
如果主要问题是与同一台机器上的其他进程有效合作(即使用尽可能多的内存,但限制进程实际可用的内存量),则可以使用作业对象或Docker容器来限制所使用的内存,与应用本身减少实际内存使用量分离,并让垃圾回收器处理其余部分。 [例如](https://dev59.com/LG025IYBdhLWcg3wAhDp)。 - Jeroen Mostert
显示剩余7条评论
1个回答

2

尽管使用Dispose模式是一个好主意,但我认为它不会在释放内存方面给您带来任何额外的好处。

有两件事可能会有所帮助:

  1. 调用GC.Collect()

但是,我真的不建议这样做(除非您遇到了内存耗尽异常)。显式调用GC.Collect()可能会影响性能,垃圾回收器本身的工作已经很好了。(但请参见下面的LOH。)

  1. 注意大对象堆(LOH)

你提到它使用了“大的内存占用”。请注意,任何单个85,000字节或以上的内存分配都来自于大对象堆(LOH)。 LOH不能像小对象堆一样进行压缩。这可能导致LOH变得分散,并且可能会导致内存不足错误,即使您有足够的可用内存。

什么情况下可能会陷入LOH?任何85,000字节或更多的内存分配,因此在64位系统上,这将是具有10,625个或更多元素的数组(或列表或字典),图像操作,大字符串等。

帮助最小化LOH碎片化的三个策略:

i.重新设计以避免它。这不总是可行的。但是,列表列表或字典字典可能会避免限制。这可能会使实现更加复杂,因此除非您真的需要,否则我不会这样做,但好的一面是这可以非常有效。

ii.使用固定尺寸。如果您在LOH中的所有或更多内存分配大小相同,则可以帮助最小化任何碎片化。例如,对于字典和列表,请将容量(设置内部数组的大小)设置为您可能要使用的最大大小。如果您正在进行图像操作,则不太实用。

iii.强制垃圾回收器压缩LOH:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;   
GC.Collect();

要使用这个功能,您需要使用.NET Framework 4.5.1或更高版本。 这可能是最简单的方法。在我的应用程序中,我有一些实例,我知道我将会进入LOH,碎片化可能会成为一个问题,所以我设置了。

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;

在析构函数中,按照标准的做法 - 但只有在分配内存时出现内存不足异常时才显式调用GC.Collect()。

希望这可以帮到你。


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