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

时间:2009-08-18 04:04:32

标签: c# .net serialization visual-studio-2008 datatable

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

这是我的代码,我发现在执行语句bformatter.Serialize(stream,table)时,存在内存不足异常。我通过任务管理器的Perormance选项卡监视内存使用情况,我发现在抛出异常时只使用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;
    }
提前谢谢, 乔治

4 个答案:

答案 0 :(得分:10)

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

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

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

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

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

答案 1 :(得分:6)

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

你将受到(越来越难)的限制

  1. 可寻址内存量
  2. CLR's limitation that no single object may consume more than 2GB of space
  3. 在可用内存中查找连续的块。
  4. 您可以发现在CLR限制为2之前空间不足,因为流中的后备缓冲区以“加倍”方式扩展,这很快导致缓冲区在大对象中分配堆。这个堆的压缩方式与其他堆的压缩方式不同(1),因此在2下构建缓冲区的理论最大大小的过程会使LOH分段,从而无法找到足够的堆在此之前发生了大量的连续区块。

    因此,如果您关闭达到限制,则缓解方法是设置流的初始容量,使其从开始到one of the constructors肯定有足够的空间。

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

    • 如果您要序列化到某个基于文件的位置,请直接将其流式传输到该位置。
    • 如果这是进入Sql Server数据库的数据,请考虑使用:
    • 如果您在内存中对此进行序列化以便进行比较,那么请考虑对正在进行比较的数据进行流式传输并随着时间的推移进行差异化。
    • 如果你在内存中持久化一个对象来重新创建它,那么这应该是一个文件或一个内存映射文件。在这两种情况下,操作系统都可以自由地构建它(在磁盘缓存或页面中映射进出主存储器)并且它可能比大多数人能够做得更好。自己。
    • 如果您这样做是为了压缩数据,那么请考虑使用流式压缩。通过添加填充,可以相当容易地将任何基于块的压缩流转换为流模式。如果您的压缩API本身不支持,请考虑使用执行或编写包装器的压缩API来执行此操作。
    • 如果您这样做是为了写入一个字节缓冲区,然后将其固定并传递给非托管函数,那么请使用UnmanagedMemoryStream,这样可以更好地分配这种缓冲区但是仍然不能保证这样做。

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


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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

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

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