JSON.NET是C#开发中必不可少的一个序列化和反序列化工具,我个人是非常喜欢用这个工具的。它不仅仅是可以序列化和反序列化。其实对于内存深拷贝的操作,我也非常喜欢用反序列化直接在内存中copy出一个全新的对象。
不过今天给大家记录一个JSON.NET反序列化成dynamic[]
对象导致内存暴涨的问题。
首先我们声明一个Person
对象,它是我们最终反序列化的对象。
class Person
{
public string Name { get; set; }
public long Id { get; set; }
public string Address { get; set; }
public string Height { get; set; }
}
然后我们通过以下的代码,将一个person数组
反序列化成字符串,然后把字符串保存到d:\person.txt
static void InitTxt()
{
List<Person> list = new List<Person>();
for (int i = 0; i < 40000; i++)
{
list.Add(new Person
{
Id = i,
Name = Guid.NewGuid().ToString(),
Address = Guid.NewGuid().ToString(),
Height = Guid.NewGuid().ToString(),
});
}
string str = JsonConvert.SerializeObject(list);
File.AppendAllText("d:\\person.txt", str);
}
注意,我们序列化之后的json数组字符串大概6M左右,截图如下:


也就是说,我们把这个perosn.txt的内容加载到内存之后,内存增加也就是6M多一点。
我这里使用的是一个WPF项目做实验,先把一个空的WPF项目运行起来,大概是40M左右,截图如下:

接下来我执行以下代码,将person.txt的内容以字符串的形式加载到对象,并且反序列化为person数组。
static void LoadPersonArray()
{
string str = File.ReadAllText("d:\\person.txt");
Person[] array = JsonConvert.DeserializeObject<Person[]>(str);
}
然后我们再来看一下内存大小大概是70M,增加了30M左右,以上代码中str对象最少会占用6M-10M的内存,反序列化出来的Person数组最少也会占用6M-10M左右的内存,加上是debug环境,内存增加30M,我认为这个是正常的。

接下来,我把上面代码中的JsonConvert.DeserializeObject<Person[]>(str);
的Person[]
改成dynamic[]
,为什么要这么改呢,因为在实际的开发逻辑中,这个person.txt的内容是从服务器的http请求拿的,并且json的结构远比示例代码中的Person复杂得多,我不想建一个单独的model去反序列化,为了方便,就 直接使用了动态类型dynamic。具体代码如下:
static void LoadDynamicArray()
{
string str = File.ReadAllText("d:\\person.txt");
dynamic[] array = JsonConvert.DeserializeObject<dynamic[]>(str);
}
当我运行起来之后,发现内存竟然直接飙升到134M,这和空的WPF项目占用的40M比起来,增加了100M。而且我只是用一个非常简单的Person对象来演示的,实际线上的项目增加的内存比这个飙升的最少两倍。

最关键的是我只是执行了一次发序列化。如果我执行多次反序列化会怎么样,于是我把代码改成下面的样子,执行了6次反序列化。
static void LoadDynamicArray()
{
string str = File.ReadAllText("d:\\person.txt");
dynamic[] array = JsonConvert.DeserializeObject<dynamic[]>(str);
dynamic[] array1 = JsonConvert.DeserializeObject<dynamic[]>(str);
dynamic[] array2 = JsonConvert.DeserializeObject<dynamic[]>(str);
dynamic[] array3 = JsonConvert.DeserializeObject<dynamic[]>(str);
dynamic[] array4 = JsonConvert.DeserializeObject<dynamic[]>(str);
dynamic[] array5 = JsonConvert.DeserializeObject<dynamic[]>(str);
}
然后我就发现我的内存飙升到了530M。

开始我以为是内存GC的问题,就在反序列化之后立刻调用GC.Collect();
回收以下内存,并不起作用。
后来我还是把反序列化的对象从dynamic[]
改成Person[]
内存才算恢复正常。
这个问题有可能是因为JSON.NET反序列化过程中对dynamic的支持不太友好,在JsonConvert.DeserializeObject
方法里面增加了一些无法释放的内存。当然也有可能是dynamic关键字本身的问题。因为我个人并没有阅读过JSON.NET的源码,所以并不清楚其中的缘由。
不过在实际开发过程中,动态类型dynamic
关键字还是要谨慎使用,尤其是dynamic
对象所表示的对象数据量比较大的情况下,要注意关注内存变化情况。