C#Winforms App - 第二代陷入困境的大型对象 - 内存问题

时间:2011-08-19 22:09:17

标签: c# winforms memory-management memory-leaks garbage-collection

我已开始对应用程序进行内存分析,因为我们最近收到了几个有关性能和内存不足异常的报告。该应用程序是用C#.Net Winforms (.Net Framework 2.0)

开发的

当应用程序启动时,ANT探查器会在Gen 2中显示17.7 MB对象。

当应用程序启动时,它会从磁盘上的xml序列化文件中读取77000+个zipcodes并保存在Hashtable中。请参阅下面的示例代码

public Class ZipCodeItem
{
    private string zipCode;
    private string city;
    private string state;
    private string county;
    private int tdhCode;
    private string fipsCounty;
    private string fipsCity;

    Public ZipCodeItem()
    {
         // Constructor.. nothing interesting here
    }

    // Bunch of public getter/setter properties
}

这是静态类,它从磁盘上的文件中读取序列化的zip数据并加载zipcodes。

internal sealed class ZipTypes
{
    private static readonly Hashtable zipCodes = new Hashtable();

    public static ArrayList LookupZipCodes(string zipCode)
    {
        if (zipCodes.Count == 0)
            LoadZipCodes();

        ArrayList arZips = new ArrayList();

        // Search for given zip code and return the matched ZipCodeitem collection
        if (zipCodes.ContainsKey(zipCode))
        {
             // Populate the array with the matched items
        }

        // Omitted the details to keep it simple

        return arZips;
    }

    private static bool LoadZipCodes()
    {
        using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
        {
            // unzip it.. Omitted the details to keep it simple
            // Read the zipcodes from the flat xml file on disk and load the local zipCodes HashTable
        }
    }
}

这个班和corr。 ZipCodes可在整个应用程序中访问。

17.7 meg Gen 2对象中大约14 meg是zipCodeItems或其子String类。

我想将我的代码更改为如何不将这些77000+ zipcode项目对象保留在内存中(在hashTable中),但在应用程序需要时提供映射的zipCode项目。

任何建议如何解决此问题?提前谢谢。

4 个答案:

答案 0 :(得分:3)

我将避免直接回答这个问题,希望能提供更有用的答案,因为我不相信与该哈希相关的~14MB实际上会导致问题。

您说您正在使用ANTS内存分析器。这是一个很棒的工具,之前我已经使用过了,所以也许我可以帮助你找到真正的问题。

我们可以安全地假设您的哈希不会导致OutOfMemoryException,因为它远远不够大。保持原样,除了两个小的改动:

  1. 使用强类型Dictionary<K,V>代替Hashtable。一旦.NET 2.0引入泛型,Hashtable基本上就被弃用了。您也可以继续使用ArrayList替换List<T>
  2. 而不是执行Contains然后在哈希中查找值,而是使用TryGetValue。这将哈希表查找的数量减少了一半。现在这可能不是您应用中的性能瓶颈,但我认为这也不等于过早优化。
  3. 现在,问题的关键......

    您有自己的探查器结果。回去看看你的内存分配位置。按顺序,检查以下内容:

    1. .NET是否拥有大部分内存,或者是本机代码(可能会创建大量实现IDisposable并且未及时调用Dispose()的对象。)如果是后者你可能知道在哪里看。
    2. 大物件堆(LOH)看起来如何?是否分配了大部分内存?许多大型分配可能会破坏LOH,并且可能在相当长的一段时间内不会被压缩。 ANTS会在结果概述页面的右上角告诉您。
    3. 事件处理程序。当对象订阅事件时,订阅者(通过MultiCastDelegate,即事件对象)存储对订户(方法)的引用。这可能导致对象的生命周期永远不会结束,并且在一段时间内,这可能会增加内存。您需要确保,如果有对象被创建然后超出范围,它还取消订阅它之前订阅的任何事件。静态事件在这里可能是一个杀手。
    4. 使用ANTS跟踪对象的生命周期。与上述类似,请确保没有对象因过时引用而无意中保持活动状态。这可能比您想象的更容易发生。再次,查看创建了相对大量对象并超出范围的区域,以及其他对象维护对它们的引用的实例。 ANTS可以在对象图中向您显示。
    5. 这至少可以让你很好地了解在哪里分配内存。您可能需要在分析器下运行程序一段时间,只需观察内存使用情况。如果它稳定上升,那么您可以执行上面列出的步骤,以尝试隔离堆积的对象。

答案 1 :(得分:0)

您将需要某种存储以及简单的访问机制。

我建议使用某种形式的基于SQL的数据库。

我们使用SQL Compact Edition取得了很大的成功,因为它可以在没有先决条件的情况下部署(也就是“私有部署”)。随后,用于查询的集成故事非常紧张 - 例如,使用Linq to SQL非常容易。

您还可以查看SQL Lite或其他提供程序。我会引导你远离SQL Express依赖,只是因为它对于这些需求来说太重了。

此外,看到您现在要在数据库中缓存邮政编码,您可能还会考虑查看某种“同步过程”(比如每日)来下载和解析您上面提到的输入XML文件(假设从某些Web服务检索XML文件),否则您只需部署已填充数据的数据库。

答案 2 :(得分:0)

  

当应用程序启动时,ANT探查器显示17.7 MB对象   住在Gen 2

我们遇到了类似的问题,我们发现可以将这些详细信息保存在备忘录中,而不是多次加载。但它不应该在启动时加载。 更改逻辑以在第一次按需使用时加载。因为可能存在根本不使用的用例。对于这种情况,没有必要在Gen2中保留这一块。

  

我确信如果你这样可能会出现严重的内存泄漏问题   你的应用程序中有内存问题。 ANT profiler非常适合   这种情况: - )

答案 3 :(得分:0)

如果您的用户抱怨内存不足异常,并且您正在处理占用15MB内存的内容,那么您的错误位置就会出现问题。

我的其余部分假设15MB真的很重要。

话虽如此,我想提供一个已经提出的SQL解决方案的替代方案(如果你确定你真的不想将15MB加载到内存中,这是很好的解决方案,具体取决于你的情况)。 / p>

我们将3GB的IP数据加载到我们的Web服务器的进程空间中。但是,大多数时候都不会访问大多数IP地址(特别是因为我们在用户群中存在很强的地理偏差,但仍需要为来自世界上不常见的地区的访问者提供数据)。

为了保持较小的内存占用和访问速度,我们使用了内存映射文件。虽然有support for them built-in to .NET 4,但您仍然可以通过任何以前的.NET版本中的互操作调用来使用它们。

内存映射文件使您可以非常快速地将文件中的数据映射到进程空间的内存中。 SQL增加了在这种情况下你可能不需要的东西的开销(事务支持,关键约束,表关系等等......一般来说都是很棒的功能但是没有必要解决这个问题,并且在性能和内存占用方面有所成本)。