序列化期间奇怪的内存不足exception

我正在使用VSTS2008 + C#+ .Net 3.5在具有12G物理内存的x64 Server 2003 Enterprise上运行此控制台应用程序。

这是我的代码,我发现在执行语句bformatter.Serialize(stream,table)时,存在内存不足exception。 我通过任务管理器的Perormance选项卡监视内存使用情况,我发现抛出exception时只使用2G物理内存,因此不应该内存不足。 🙁

有什么想法有什么不对? .Net序列化的任何限制?

static DataTable MakeParentTable() { // Create a new DataTable. System.Data.DataTable table = new DataTable("ParentTable"); // Declare variables for DataColumn and DataRow objects. DataColumn column; DataRow row; // Create new DataColumn, set DataType, // ColumnName and add to DataTable. column = new DataColumn(); column.DataType = System.Type.GetType("System.Int32"); column.ColumnName = "id"; column.ReadOnly = true; column.Unique = true; // Add the Column to the DataColumnCollection. table.Columns.Add(column); // Create second column. column = new DataColumn(); column.DataType = System.Type.GetType("System.String"); column.ColumnName = "ParentItem"; column.AutoIncrement = false; column.Caption = "ParentItem"; column.ReadOnly = false; column.Unique = false; // Add the column to the table. table.Columns.Add(column); // Make the ID column the primary key column. DataColumn[] PrimaryKeyColumns = new DataColumn[1]; PrimaryKeyColumns[0] = table.Columns["id"]; table.PrimaryKey = PrimaryKeyColumns; // Create three new DataRow objects and add // them to the DataTable for (int i = 0; i <= 5000000; i++) { row = table.NewRow(); row["id"] = i; row["ParentItem"] = "ParentItem " + i; table.Rows.Add(row); } return table; } static void Main(string[] args) { DataTable table = MakeParentTable(); Stream stream = new MemoryStream(); BinaryFormatter bformatter = new BinaryFormatter(); bformatter.Serialize(stream, table); // out of memory exception here Console.WriteLine(table.Rows.Count); return; } 

乔治,提前谢谢

注意: DataTable默认使用1. *中使用的xml序列化格式,这非常低效。 要尝试的一件事是切换到更新的格式

  dt.RemotingFormat = System.Data.SerializationFormat.Binary; 

重新出现内存/ 2GB; 各个.NET对象(例如MemoryStream后面的byte[] )限制为2GB。 也许尝试写一个FileStream而不是?

(编辑:nope:尝试过,仍然是错误)

我还想知道你是否可以使用table.WriteXml(stream)获得更好的结果(在这种情况下table.WriteXml(stream) ,如果空间是溢价的话,也许可以使用GZIP等压缩。

正如已经讨论的那样,这是尝试获得Gigabyte大小的连续内存块的基本问题。

你会受到限制(越来越难)

  1. 可寻址内存的数量
    • 因为你是64位,这将是你12GB的物理内存,减去设备所需的任何漏洞加上任何交换文件空间。
    • 请注意,您必须运行具有相关PE标头的应用程序,表明它可以运行64位,或者您将在WoW64下运行,并且只有4GB的地址空间。
    • 另请注意,默认目标在2010年发生了变化 ,这是一个有争议的变化 。
  2. CLR的限制是没有单个对象可能消耗超过2GB的空间 。
  3. 在可用内存中查找连续块。

您可以发现在CLR限制为2之前用完了空间,因为流中的后备缓冲区以“双倍”方式扩展,这很快导致缓冲区在大对象堆中分配。 这个堆的压缩方式与其他堆的压缩方式不同(1),因此构建到2的缓冲区的理论最大大小的过程会使LOH分段,因此您无法在之前找到足够大的连续块有时候是这样的。

因此,如果接近极限,则缓解方法是设置流的初始容量,使其从一开始通过其中一个构造函数确实具有足够的空间。

鉴于您作为序列化过程的一部分写入内存流,因此有意义的是实际按预期使用流并仅使用所需的数据。

  • 如果您要序列化到某个基于文件的位置,则直接将其流式传输到该位置。
  • 如果这是进入Sql Server数据库的数据,请考虑使用:
    • FILESTREAM 2008只有我害怕。
    • 从2005年开始,您可以读取/写入块,但写入并未很好地集成到ADO.Net中
    • 对于2005年之前的版本,存在相对令人不快的变通方法
  • 如果你在内存中序列化这个用于比较,那么考虑流式传输被比较的数据并随着你的方向进行差异化。
  • 如果你在内存中持久化一个对象来重新创建它,那么这应该是一个文件或一个内存映射文件。 在这两种情况下,操作系统都可以自由地构建它(在磁盘缓存或页面中映射进出主存储器),并且它可能比大多数人能够做得更好。他们自己。
  • 如果您这样做以便可以压缩数据,那么请考虑使用流式压缩。 通过添加填充,可以相当容易地将任何基于块的压缩流转换为流模式。 如果您的压缩API本身不支持,请考虑使用执行或编写包装器的压缩API来执行此操作。
  • 如果您这样做是为了写入一个字节缓冲区,然后将其固定并传递给非托管函数,那么请使用UnmanagedMemoryStream ,这样可以更好地分配这种大小的缓冲区,但仍然无法保证这样做。

也许如果你告诉我们你正在序列化这个大小的对象,我们或许可以告诉你更好的方法来做到这一点。


  1. 这是您不应该依赖的实现细节

1)操作系统是x64,但是app x64(或anycpu)? 如果没有,它的上限为2Gb。

2)这是在’早期’发生,还是在应用程序运行一段时间后(即后来的n个序列化)? 它可能是大对象堆碎片的结果……?

有趣的是,它在实现内存错误之前实际上达到了3.7GB(Windows 7 x64)。 显然,它需要大约两倍才能完成。

鉴于应用程序在创建表后使用1.65GB,看起来它可能达到了2GB byte[] (或任何单个对象)限制Marc Gravell所说的(1.65GB + 2GB~ = 3.7GB)

基于这篇博客 ,我想您可以使用WINAPI分配内存,并使用它编写自己的MemoryStream实现。 也就是说,如果你真的想这样做。 或者当然使用多个数组编写一个:)