序列化/反序列化后使用的 RAM 大小异常增加



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

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

现在:GC 旨在优化您的整体系统性能;它不是为了不断主动回收内存而设计的,原因有很多:

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

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

关键点:数组的上限硬上限;您不能将当前的实现扩展到 2GB 以上(即使启用了<gcAllowVeryLargeObjects>)。

此外,我怀疑BinaryFormatter正在加剧事情 - 它几乎总是如此。存在替代的更高效的常用序列化程序。减小序列化大小将是要考虑的替代选项,而不是 - 或与 - 移动到基于Stream的模型结合使用。

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

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

序列化和压缩阶段已经完全Stream兼容。如果你能让加密层Stream兼容,你就是一个赢家。

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

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

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

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

[此问题对您很有帮助:何时应在 .NET 中释放对象?

最新更新