我刚刚向第一位客户交付了我的第一个 C# WebAPI 应用程序。在正常负载下,最初的性能甚至比我预期的还要好。最初。
一切正常,直到某个时刻,内存已满,垃圾收集开始暴乱(如“它收集还不是垃圾的对象”)。那时,有多个 W3WP 线程,总共有大约 10 GB 的 RAM,每个工作人员只有个位数的 GIG。重启 IIS 后一切恢复正常,但当然内存使用率再次上升。
如果我说错了请指正
- C# 不应该有自动垃圾收集吗?
- GC 不应该很容易地收集 WebAPI 应用程序的垃圾吗?
请帮帮我:
- 我如何明确说明 GC 应该收集什么,从而防止内存泄漏?
someBigList = null;是要走的路吗? - 如何检测内存泄漏的位置?
编辑:让我澄清一些事情。
我的 .NET WebAPI 应用程序主要是一堆
public class MyApiController:ApiController
{
[HttpGet]
public MyObjectClass[] MyApi(string someParam) {
List<MyObjectClass> list = new List<MyObjectClass>();
...
for/while/foreach {
MyObjectClass obj = new MyObjectClass();
obj.firstStringAttribute = xyz;
...
list.Add(obj);
}
return list.ToArray();
}
}
在这种情况下,GC应该很容易:“return”之后,所有局部变量都应该是垃圾。然而,随着每次调用,使用的内存都会增加。
我最初认为 C# WebAPI 程序的行为类似于(预编译的)PHP:IIS 调用该程序,它被执行,返回值,然后完全处理掉。
但事实并非如此。例如,我发现静态变量可以在运行之间保留它们的数据,现在我处理了所有静态变量。
因为我发现静态变量是 GC 的问题:
internal class Helper
{
private static List<string> someVar = new List<string>();
internal Helper() {
someVar=new List<string>();
}
internal void someFunc(string str) {
someVar.Add(str);
}
internal string[] someOtherFunc(string str) {
string[] s = someVar.ToArray();
someVar=new List<string>();
return s;
}
}
这里,在内存不足的情况下,someVar 抛出了一个空指针错误,我认为这只能是由 GC 引起的,因为我没有找到任何 someVar 被我主动取消的代码.
我认为内存增加速度变慢了,因为我主动将最常用的 Controller 中最大的数组变量设置为 null,但这只是一种直觉,甚至不是一个完整的解决方案。
我现在将使用您提供的链接进行一些分析,然后返回一些结果。
请您参考如下方法:
Shouldn't C# have automatic garbage collection?
C# 是 .NET 运行时的编程语言,.NET 将自动垃圾收集带到了桌面。所以,是的,尽管从技术上讲,C# 并不是带来它的部分。
Shouldn't it be easy for GC to collect the garbage of a WebAPI application?
当然,它应该与任何其他类型的 .NET 应用程序一样简单。
这里的共同主题是垃圾。 .NET 如何确定某些东西是垃圾?通过验证不再有对该对象的实时引用。老实说,与垃圾收集器中存在“它收集还不是垃圾的对象”的严重错误相比,您更有可能错误地验证了您的假设之一。
要查找泄漏,您需要找出内存中当前保存的对象,确定它是否正确,如果不正确,则找出是什么将它们保存在那里。内存分析器应用程序会对此有所帮助,有许多可用的应用程序,例如 Red-Gate ANTS Memory Profiler .
对于您的其他问题,如何使某些东西符合垃圾回收条件?通过把它变成垃圾(见上面的定义)。请注意,将局部变量设置为 null 可能不一定有帮助或不需要。然而,将静态变量设置为 null,可能。但确定这一点的正确方法是使用分析器。
这里有一些您可能会研究的暗中拍摄类型的提示:
- 查看静态类、静态字段和静态属性。您是否在那里存储正在累积的数据?
- 静态事件怎么样?你有这个吗?当您不再需要该事件时,您记得取消订阅吗?
- 我所说的“静态字段、属性和事件”还指直接或间接存储在静态字段或属性中的对象中包含的普通实例字段、属性和事件。基本上,任何可以将对象保存在内存中的东西。
- 您是否记得
Dispose您所有的IDisposable对象?如果不是,则正在使用的内存可能是非托管的。然而,通常情况下,当垃圾收集器收集托管对象时,该对象的终结器也应该清理非托管内存,但是您可能会分配 GC 算法不知道的内存,因此认为它不是等待收集的大问题。查看GC.AddMemoryPressure有关更多信息的方法。
