问题:
在一个高并发的接口经常会报错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));
|