所使用的ram的大小在序列化/反序列化后异常增加

时间:2017-06-09 13:28:15

标签: c# memory serialization

我使用以下方法在序列化后将应用程序数据保存到文件中,并在反序列化(en / decrypted)后从该文件加载数据。

private void SaveClassToFile(string fileAddress, string password, object classToSave)
{
    const int ivSaltLength = 16;
    byte[] salt = new byte[ivSaltLength];
    byte[] iv = new byte[ivSaltLength];
    byte[] codedClass = new byte[0];

    iv = CreateIV();
    salt = CreateSalt();

    using (var memoryStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(memoryStream, classToSave);
        codedClass = new byte[Convert.ToInt32(memoryStream.Length)];
        memoryStream.Seek(0, SeekOrigin.Begin);

        if (memoryStream.Read(codedClass, 0, Convert.ToInt32(memoryStream.Length)) != memoryStream.Length)
        {throw new Exception("failed to read from memory stream"); }
    }

    using (SpecialCoderDecoder specialCoder = new SpecialCoderDecoder(SpecialCoderDecoder.Type.Coder, password, salt, iv))
    { specialCoder.Code(codedClass); }

    using (FileStream streamWriter = new FileStream(fileAddress, FileMode.CreateNew))
    using (BinaryWriter binaryWriter = new BinaryWriter(streamWriter))
    {
        binaryWriter.Write(salt);
        binaryWriter.Write(iv);
        binaryWriter.Write(codedClass);
    }
}

private object LoadClassFromFile(string fileAddress, string password)
{
    const int ivSaltLength = 16;
    byte[] salt = new byte[ivSaltLength];
    byte[] iv = new byte[ivSaltLength];
    byte[] codedClass = new byte[0];
    int codedClassLengthToRaed = 0;
    FileInfo fileInfo;
    object result = null;
    fileInfo = new FileInfo(fileAddress);

    using (FileStream streamWriter = new FileStream(fileAddress, FileMode.Open))
    using (BinaryReader binaryreader = new BinaryReader(streamWriter))
    {
        salt = binaryreader.ReadBytes(ivSaltLength);
        iv = binaryreader.ReadBytes(ivSaltLength);
        codedClassLengthToRaed = Convert.ToInt32(fileInfo.Length) - (2 * ivSaltLength);
        codedClass = binaryreader.ReadBytes(codedClassLengthToRaed);
    }

    using (SpecialCoderDecoder specialDecoder = new SpecialCoderDecoder(SpecialCoderDecoder.Type.Decoder, password, salt, iv))
    { specialDecoder.Decode(codedClass); }

    using (MemoryStream memoryStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        memoryStream.Write(codedClass, 0, codedClass.Length);

        memoryStream.Seek(0, SeekOrigin.Begin);
        result = (object)binaryFormatter.Deserialize(memoryStream);
    }

    return result;
}

如果应用程序中没有数据,我向它添加大约100MB的数据(基于任务管理器)并保存。加载数据后,任务管理器显示应用程序数据大约为200-400 MB!

为了将应用程序类封装到一个类中以便使用此方法,我使用类似的类:

public class BigClass
{
    public  ClassA classA;
    public ClassB classB;

    public BigClass(ClassA a, ClassB b)
    {
        classA = a;
        classB = b;
    }
}

每个ClassAClassB(应该保存/加载的类)都是这样的:

public class ClassA
{
    List<ClassASub> list = new List<ClassASub>();

    //some variables...

    //some methodes

    private class ClassASub
    {
        int intValue;
        long longValue;
        string stringValue;
        Image image;

        //some simple methodes....
    }
}

我没有谈论序列化/反序列化过程中使用的RAM的大小。在此之后,当只有应用程序数据存在时,我会谈论使用过的RAM。

2 个答案:

答案 0 :(得分:4)

您将数据作为数组(codedClass)加载到内存中。根据你的指示,这个数组可能大约为100MB,更多足以确保它在大对象堆上分配。

现在:GC旨在优化您的整体系统性能;由于多种原因,旨在不断积极地回收内存:

  • 如果您的系统中有大量可用内存(您没有内存压力)并且没有特别需要收集,那么这是不必要的开销
  • 有些数据比其他数据更昂贵;这些最昂贵的是大对象堆,因此它会进入队列的后面;其他内存首先发布
  • 即使数据是免费的,将这些页面释放回操作系统也不一定有利<;该过程可以有效地决定保留它们以避免不断询问操作系统的内存并将其交还回来

在你的情况下,你可以尝试使用System.GC上的方法来强行运行集合,但我认为真正的目标是不分配那些大阵列。如果你可以做任何事情来转移到基于Stream的模型而不是基于数组的模型,那就太好了。这可能意味着显着改变SpecialCoderDecoder

关键点:阵列的上部大小是硬帽;你无法将当前的实现扩展到2GB以上(即使启用了<gcAllowVeryLargeObjects>)。

此外,我怀疑BinaryFormatter正在加剧事情 - 它几乎总是如此。存在替代的更有效的陈旧序列化器。减少序列化大小将是另一种选择,可以代替 - 或与 - 结合 - 转移到基于Stream的模型。

此外:您可以尝试在加密的有效负载中使用压缩技术(GZipStreamDeflateStream等) 。您不应尝试压缩加密数据 - 您需要确保订单是:

Serialize -> Compress -> Encrypt -> (storage) -> Decrypt -> Decompress -> Deserialize

序列化和压缩阶段已完全Stream - 兼容。如果您可以使加密图层Stream兼容,那么您就是胜利者。

答案 1 :(得分:-1)

您创建的类包含大量数据(例如,A类,B类,BigClass)。 每当你创建和使用包含许多数据的类(特别是值类型)时,你必须告诉运行时当你不再需要它们时销毁它们(或处理它们)。 这被称为&#34; Dispose Pattern&#34;你可以在这里找到更多相关信息:

https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern

一些.net类内置了Dispose()方法,以便Net Garbage Collector(GC)知道何时将它们擦除内存。但不是全部。 对于那些拥有Dispose()并实现IDisposable接口的人,你可以使用&#34;使用&#34;声明在任务完成时自动处理它们(您在代码中使用了一些using语句,但没有在所有必需的地方使用)。

简单的答案是:序列化完成后,您的数据仍保留在内存中。让你的课程一次性使用,并在你不需要时处理它们。

[这个问题对您有帮助:When should I dispose my objects in .NET?]