問題:
在一個高並發的接口經常會報錯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));