String引起的OutOfMemory异常 + 如何计算C#对象所占内存的大小
问题:
在一个高并发的接口经常会报错OutOfMemory,检查了代码和服务器各种配置之后感觉一切都正常……
百思不得其解,只能把报错的一段拿出来测试,
最后发现是黄色这段代码出了问题:
1 public void TestOutOfMemory() 2 { 3 var result = string.Empty; 4 string BSID = "MH_SYS"; 5 string FType = "USR"; 6 DirectoryInfo outFolder = new DirectoryInfo(ConfigurationManager.AppSettings["filePath"]); 7 var temp = outFolder.GetDirectories().Where(x => !x.Name.Contains("bak")); 8 if (temp.Count() > 0) 9 { 10 try 11 { 12 GC.Collect(); 13 GC.WaitForFullGCComplete(); 14 long start = GC.GetTotalMemory(true); 15 16 var timeSpan = temp.OrderByDescending(x => x.Name).FirstOrDefault().Name;//获取timeSpan文件夹名称 17 DirectoryInfo inFolder = new DirectoryInfo(ConfigurationManager.AppSettings["filePath"] + timeSpan + @"\" + BSID + @"\" + FType + @"\"); 18 if (inFolder.GetFiles().Count() > 0) 19 { 20 var list = inFolder.GetFiles().OrderBy(x => Convert.ToInt16(x.Name.Split('.')[0])); 21 foreach (var item in list) 22 { 23 using (FileStream fs = new FileStream(item.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) 24 { 25 int fsLen = (int)fs.Length; 26 byte[] heByte = new byte[fsLen]; 27 int r = fs.Read(heByte, 0, heByte.Length); 28 result += System.Text.Encoding.UTF8.GetString(heByte); 29 } 30 } 31 } 32 33 GC.Collect(); 34 GC.WaitForFullGCComplete(); 35 long end = GC.GetTotalMemory(true); 36 long size = end - start; 37 38 Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + "\n" + (size/1024/1024) + "M\n" + result.GetHashCode() + "\n" + result.Substring(0,1000) + "\n\n\n\n"); 39 } 40 catch (Exception e) 41 { 42 throw e; 43 } 44 } 45 }
用日志记录了下result这个String字符串的哈希编码,发现在多个并发的情况下,都是一样的,说明GC并没有及时回收这个String。
也就是说接口并发时用的都是同一个String对象,加上接口所需要返回的内容很大,每个大概有30M左右,测试当5个并发的时候,占用内存就到了600-700M,10个并发的时候内存占用到了1.5G左右,所以OutOfMemory也不奇怪啦。
PS:计算C#对象所占内存的大小
请参考上面代码中灰色部分~~
解决方案:
找到问题根源之后很简单,只要用StringBuilder代替String,用下面代码替换上文黄色部分即可
StringBuilder result = new StringBuilder(); result.Append(System.Text.Encoding.UTF8.GetString(heByte));