可能引发System.OutOfMemoryException的情况和我们能采取的措施

当调用 StringBuilder.Insert 方法

尝试增加 StringBuilder 对象的长度超出其 StringBuilder.MaxCapacity 属性指定的大小。 下面的示例演示了在示例尝试插入将导致对象的 Length 属性超过其最大容量的字符串时,调用 StringBuilder.Insert(Int32, String, Int32) 方法所引发 OutOfMemoryException 异常。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      StringBuilder sb = new StringBuilder(15, 15);
      sb.Append("Substring #1 ");
      try {
         sb.Insert(0, "Substring #2 ", 1);
      }
      catch (OutOfMemoryException e) {
         Console.WriteLine("Out of Memory: {0}", e.Message);
      }
   }
}
// The example displays the following output:
//    Out of Memory: Insufficient m

可以执行以下任一操作来解决该错误:

  • 将对 StringBuilder.StringBuilder(Int32, Int32) 构造函数的调用替换为调用任何其他 StringBuilder 构造函数重载。 StringBuilder 对象的最大容量将设置为其默认值,该值是 Int32.MaxValue的。

  • 使用足够大的 maxCapacity 值调用 StringBuilder.StringBuilder(Int32, Int32) 构造函数,以容纳对 StringBuilder 对象的任何扩展。

您的应用程序作为32位进程运行,确要更多的内存

32位进程最多可以在32位系统上分配2GB 的虚拟用户模式内存,在64位系统上分配4GB 的虚拟用户模式内存。 这会使公共语言运行时在需要大量分配时更难分配足够的连续内存。 与此相反,64位进程最多可分配8TB 的虚拟内存。 若要解决此异常,请重新编译你的应用以面向64位平台。 

应用正在泄漏非托管资源

虽然垃圾回收器能够释放分配给托管类型的内存,但它不管理分配给非托管资源(包括文件、内存映射文件、管道、注册表项和等待句柄的句柄)的内存和由 Windows API 调用直接分配的内存块,或通过调用内存分配函数(如 malloc)分配的内存块。 使用非托管资源的类型实现 IDisposable 接口。

如果你使用的是使用非托管资源的类型,则应确保在使用完该类型时调用其 IDisposable.Dispose 方法。 (某些类型还实现 Close 方法,该方法在函数中与 Dispose 方法相同。)如果已创建使用非托管资源的类型,请确保已实现 Dispose 模式,并在必要时提供终结器。 

你正在尝试在64位进程中创建一个大型数组

默认情况下,.NET Framework 中的公共语言运行时不允许大小超过2GB 的单个对象。 若要替代此默认值,可以使用<gcAllowVeryLargeObjects >配置文件设置来启用总计大小超过 2 GB 的数组。 在 .NET Core 中,默认情况下会启用支持大于 2 GB 的数组。

在内存中使用非常大的数据集(如数组、集合或数据库数据集)

当内存中驻留的数据结构或数据集变得很大时,公共语言运行时无法为它们分配足够的连续内存,OutOfMemoryException 异常结果。

若要防止 OutOfMemoryException 异常,必须修改应用程序,使内存中驻留的数据较少,或将数据分成需要较小的内存分配的段。 例如:

  • 如果要从数据库中检索所有数据,然后在应用程序中对其进行筛选,以最大程度地减少到服务器的往返,则应修改查询以仅返回应用程序所需的数据的子集。 处理大型表时,多个查询几乎始终比检索单个表中的所有数据,然后对其进行操作的效率更高。

  • 如果执行的是用户动态创建的查询,则应确保查询返回的记录数受到限制。

  • 如果你使用的大型数组或其他集合对象的大小会导致 OutOfMemoryException 异常,则应将应用程序修改为使用子集中的数据,而不是一次处理全部数据。

下面的示例获取一个包含200000000浮点值的数组,然后计算其平均值。 该示例的输出显示,因为该示例在计算平均值之前将整个数组存储在内存中,因此会引发 OutOfMemoryException

using System;
using System.Collections.Generic;

public class Example
{
   public static void Main()
   {
      Double[] values = GetData();
      // Compute mean.
      Console.WriteLine("Sample mean: {0}, N = {1}",
                        GetMean(values), values.Length);
   }

   private static Double[] GetData()
   {
      Random rnd = new Random();
      List<Double> values = new List<Double>();
      for (int ctr = 1; ctr <= 200000000; ctr++) {
         values.Add(rnd.NextDouble());
         if (ctr % 10000000 == 0)
            Console.WriteLine("Retrieved {0:N0} items of data.",
                              ctr);
      }
      return values.ToArray();
   }

   private static Double GetMean(Double[] values)
   {
      Double sum = 0;
      foreach (var value in values)
         sum += value;

      return sum / values.Length;
   }
}
// The example displays output like the following:
//    Retrieved 10,000,000 items of data.
//    Retrieved 20,000,000 items of data.
//    Retrieved 30,000,000 items of data.
//    Retrieved 40,000,000 items of data.
//    Retrieved 50,000,000 items of data.
//    Retrieved 60,000,000 items of data.
//    Retrieved 70,000,000 items of data.
//    Retrieved 80,000,000 items of data.
//    Retrieved 90,000,000 items of data.
//    Retrieved 100,000,000 items of data.
//    Retrieved 110,000,000 items of data.
//    Retrieved 120,000,000 items of data.
//    Retrieved 130,000,000 items of data.
//
//    Unhandled Exception: OutOfMemoryException.

下面的示例通过处理传入的数据,无需将整个数据集存储在内存中,将数据序列化到文件(在示例中注释掉这些行,因为在这种情况下,它们生成的文件的大小大于 1 GB),并将计算所得的平均值和事例数返回到调用例程,从而消除了 OutOfMemoryException 异常。

using System;
using System.IO;

public class Example
{
   public static void Main()
   {
      Tuple<Double, long> result = GetResult();
      Console.WriteLine("Sample mean: {0}, N = {1:N0}",
                        result.Item1, result.Item2);
   }

   private static Tuple<Double, long> GetResult()
   {
      int chunkSize = 50000000;
      int nToGet = 200000000;
      Random rnd = new Random();
      // FileStream fs = new FileStream(@".\data.bin", FileMode.Create);
      // BinaryWriter bin = new BinaryWriter(fs);
      // bin.Write((int)0);
      int n = 0;
      Double sum = 0;
      for (int outer = 0;
           outer <= ((int) Math.Ceiling(nToGet * 1.0 / chunkSize) - 1);
           outer++) {
         for (int inner = 0;
              inner <= Math.Min(nToGet - n - 1, chunkSize - 1);
              inner++) {
            Double value = rnd.NextDouble();
            sum += value;
            n++;
            // bin.Write(value);
         }
      }
      // bin.Seek(0, SeekOrigin.Begin);
      // bin.Write(n);
      // bin.Close();
      return new Tuple<Double, long>(sum/n, n);
   }
}
// The example displays output like the following:
//    Sample mean: 0.500022771458399, N = 200,000,000

反复串联大型字符串

因为字符串是不可变的,所以每个字符串串联操作都会创建一个新字符串。 对于小字符串或少量串联操作的影响,可忽略不计。 但对于大型字符串或大量串联操作,字符串连接可能会导致大量的内存分配和内存碎片,并导致性能不佳,并且可能 OutOfMemoryException 异常。

在连接大型字符串或执行大量串联操作时,应使用 StringBuilder 类,而不是 String 类。 完成操作字符串后,通过调用 StringBuilder.ToString 方法将 StringBuilder 实例转换为字符串。

将大量对象固定在内存中

长时间将大量对象固定在内存中可能会使垃圾回收器难以分配连续的内存块。 如果已将大量对象固定在内存中(例如,通过使用中C#的 fixed 语句或通过将 GCHandle.Alloc(Object, GCHandleType) 方法与 GCHandleType.Pinned的句柄类型一起调用),则可以执行以下操作来解决 OutOfMemoryException 异常。

  • 评估是否确实需要固定每个对象,

  • 确保每个对象尽快取消固定。

  • 请确保每次调用 GCHandle.Alloc(Object, GCHandleType) 方法来固定内存时都有相应的 GCHandle.Free 方法调用来取消固定该内存。

以下 Microsoft 中间(MSIL)指令引发 OutOfMemoryException 异常:

  • 文本框
  • newarr
  • newobj

 

posted on 2020-07-16 08:17  活着的虫子  阅读(1543)  评论(0编辑  收藏  举报

导航