【问题标题】:Size of ram used increase abnormally after serialization/deserialization序列化/反序列化后使用的 ram 大小异常增加
【发布时间】:2017-06-09 21:47:52
【问题描述】:

我使用以下方法在序列化后将应用程序数据保存到文件中,并在反序列化(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,那时应该只存在应用程序数据。

【问题讨论】:

标签: c# memory serialization


【解决方案1】:

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

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

  • 如果您的系统中有大量可用内存(您没有处于内存压力之下)并且没有特别需要收集,则这是不必要的开销
  • 某些数据的收集成本高于其他数据;其中最昂贵的是大对象堆,所以它排在队列的后面;其他内存被释放首先
  • 即使数据是免费的,将这些页面释放回操作系统也不是必要有利的;该进程可以有效地决定保留它们,以避免不断向操作系统请求内存并将其归还

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

关键点:一个数组的大小上限 hard a hard cap;您无法将当前实现扩展到 2GB 以上(即使启用了&lt;gcAllowVeryLargeObjects&gt;)。

此外,我怀疑BinaryFormatter 正在使事情恶化——几乎总是如此。存在替代更有效的陈旧序列化程序。减小 序列化大小 将是一个可供考虑的替代选择,可以替代 - 或结合 - 迁移到基于 Stream 的模型。

另外:您可以尝试使用压缩技术(GZipStreamDeflateStream 等)在加密的负载中。您不应尝试压缩加密数据 - 您需要确保顺序为:

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

序列化和压缩阶段已经完全兼容Stream。如果您可以使加密层Stream 兼容,那么您就是赢家。

【讨论】:

  • ♦ 感谢您的回答。但是当我删除一些数据时,它会导致已用内存减少(基于任务管理器)。例如加载后,如果我删除一半的数据,使用的内存将变成大约一半。这是否意味着问题出在其他地方并且每个班级的人数都增加了?你说的是真的,但在结束加载进度后,我可以看到分配的空间已释放。还是我想错了,并不代表我的想法?
  • @Student 我认为这需要比我在这里提供的更多的专门调查才能给出结论性的答案,
  • ♦ 感谢您的回答和您的时间。我应该测试和搜索更多。如果我找到一个好的答案,我会在这里发布
【解决方案2】:

您创建的类包含大量数据(例如 A 类、B 类、BigClass)。 每当您创建和使用此类包含许多数据(特别是值类型)的类时,您必须告诉运行时在您不再需要它们时销毁(或处置)它们。 这称为“处理模式”,您可以在此处找到更多信息:

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

一些 .net 类内置了 Dispose() 方法,因此 .Net 垃圾收集器 (GC) 知道何时将它们从内存中清除。但不是全部。 对于那些拥有 Dispose() 并实现 IDisposable 接口的人,您可以使用“Using”语句在他们的任务完成时自动处理它们(您在代码中使用了一些 using 语句,但不是在所有必需的地方)。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    • 2012-12-12
    • 1970-01-01
    相关资源
    最近更新 更多