无法检测到内存泄漏

时间:2012-07-24 02:07:46

标签: c# garbage-collection

我有一个Stock类,它从文件中加载大量的库存数据历史记录(大约100 MB)。我有一个Pair类,它接受两个Stock个对象,并计算两者之间的一些统计关系,然后将结果写入文件。

在我的主要方法中,我有一个循环通过一对股票列表(约500)。它创建了2个库存对象,然后创建了两个对象。此时,对计算被写入文件,我完成了对象。我需要释放内存,以便继续进行下一次计算。

除了将3个对象设置为null之外,我在循环结束时添加了以下两行:

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();

踩过这两条线似乎只是从每次循环迭代分配的200-300 MB中释放出50 MB(从任务管理器中查看)。

该程序在给出系统内存异常之前会执行大约八对或十对。内存使用量稳步增加,直到崩溃大约1.5 GB。 (这是运行Win7 Ultimate的8 GB机器)

我对垃圾收集没有多少经验。我做错了吗?

以下是我的代码,因为您问过:(注意:程序有两种模式,1>添加模式,其中新对添加到系统.2>常规模式,根据filesystemwatcher事件实时更新配对文件。库存数据由名为QCollector的外部应用程序更新。)

这是MainForm中以添加模式运行的片段:

foreach (string line in PairList)
{
    string[] tokens = line.Split(',');

    stockA = new Stock(QCollectorPath, tokens[0].ToUpper()); 
    stockB = new Stock(QCollectorPath, tokens[1].ToUpper()); 

    double ratio = double.Parse(tokens[2]);
    Pair p = new Pair(QCollectorPath, stockA, stockB, ratio);

    // at this point the pair is written to file (constructor handles this)        

    // commenting out the following lines of code since they don't fix the problem
    // stockA = null;
    // stockB = null;
    // p = null;

    // refraining from forced collection since that's not the problem
    // GC.Collect(GC.MaxGeneration);
    // GC.WaitForPendingFinalizers();

    // so far this is the only way i can fix the problem by setting the pair classes
    // references to StockA and StockB to null
    p.Kill();
}

我根据请求添加了更多代码:StockPairTimeSeries的子类,具有通用功能

public abstract class TimeSeries {
     protected List<string> data;

     // following create class must be implemented by subclasses (stock, pair, etc...)
     // as each class is created differently, although their data formatting is identical
     protected void List<string> Create();

     // . . . 

     public void LoadFromFile()
     {
          data = new List<string>();

          List<StreamReader> srs = GetAllFiles();

          foreach (StreamReader sr in srs)
          {
               List<string> temp = new List<string>();
               temp = TurnFileIntoListString(sr);
               data = new List<string>(temp.Concat(data));
               sr.Close()
          }
     }

     // uses directory naming scheme (according to data month/year) to find files of a symbol
     protected List<StreamReader> GetAllFiles()...

     public static List<string> TurnFileIntoListString(StreamReader sr)
     {
          List<string> list = new List<string>();
          string line;
          while ((line = sr.ReadLine()) != null)
               list.Add(line);
          return list;
     }

     // this is the only mean to access a TimeSeries object's data
     // this is to prevent deadlocks by time consuming methods such as pair's Create

     public string[] GetListCopy()
     {
          lock (data)
          {
               string[] listCopy = new string[data.count];
               data.CopyTo(listCopy);
               return listCopy();
          }
     }
}

public class Stock : TimeSeries
{
     public Stock(string dataFilePath, string symbol, FileSystemWatcher fsw = null)
     {
          DataFilePath = dataFilePath;
          Name = symbol.ToUpper();
          LoadFromFile();
          // to update stock data when external app updates the files
          if (fsw != null) fsw.Changed += FileSystemEventHandler(fsw_Changed);
     }

     protected void List<string> Create()
     {
          // stock files created by external application
     }


     // . . . 
}

public class Pair : TimeSeries {
     public Pair(string dataFilePath, Stock stockA, Stock stockB, double ratio)
     {
          // assign parameters to local members
          // ...         

          if (FileExists())
               LoadFromFile();
          else
             Create();
     }

     protected override List<string> Create()
     {
          // since stock can get updated by fileSystemWatcher's event handler
          // a copy is obtained from the stock object's data 
          string[] listA = StockA.GetListCopy();
          string[] listB = StockB.GetListCopy();
          List<string> listP = new List<string>();

          int i, j;
          i = GetFirstValidBar(listA);
          j = GetFirstValidBar(listB);
          DateTime dtA, dtB;

          dtA = GetDateTime(listA[i]);
          dtB = GetDateTime(listB[j]);

          // this hidden segment adjusts i and j until they are starting at same datetime
          // since stocks can have different amount of data

          while (i < listA.Count() && j < listB.Count)
          {
              double priceA = GetPrice(listA[i]);
              double priceB = GetPrice(listB[j]);
              double priceP = priceA * ratio - priceB;
              listP.Add(String.Format("{0},{1:0.00},{2:0.00},{3:0.00}"
                   , dtA
                   , priceP
                   , priceA
                   , priceB
              );
              if (i < j)
                   i++;
              else if (j < i)
                   j++;
              else
              {
                   i++; 
                   j++;
              }
          }
     }

     public void Kill()
     {
         data = null;
         stockA = null;
         stockB = null;
     }
 }

2 个答案:

答案 0 :(得分:3)

你的内存泄漏在这里:

if (fsw != null) fsw.Changed += FileSystemEventHandler(fsw_Changed);

只要FileSystemWatcher处于活动状态,stock对象的实例将保留在内存中,因为它正在响应FileSystemWatcher的事件。

我认为您要在其他地方实现该事件,或者在代码中的其他位置添加:

if (fsw != null) fsw.Changed -= fsw_Changed;

考虑到编写代码的方式,在完成批量处理的情况下,有可能在没有FileSystemWatcher的情况下调用stock对象。

在您发布的原始代码中,使用FileSystemWatcher调用Stock类的构造函数。你现在改变了。我想你会发现现在使用null FileSystemWatcher你可以删除你的kill并且你不会有泄漏,因为你不再听fsw.Changed了。

答案 1 :(得分:0)

每个Stock类都会从大文件中加载大量的库存数据历史记录,对吧?因此,您必须在代码中的某个位置打开这些文件。你有没有正确处理这些文件?如果没有,那些将继续打开,Unmanaged Resource会耗尽你的记忆。使用using块来正确处理打开的文件。 This可能有助于实施适当的处置模式。