C#代码加载新窗体触发System.OutOfMemoryException问题求助
System.OutOfMemoryException异常排查与解决
异常信息
System.OutOfMemoryException: 'Out of memory.'
An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll
问题背景
加载新窗体时触发上述异常,调试显示当前托管内存占用仅37MB,多次尝试修复无果,相关代码如下:
相关代码
public static async Task<List<Meni>> RetrieveAll() { using (var handler = new LoggingHttpClientHandler()) using (var client = new HttpClient(handler)) { client.Timeout = TimeSpan.FromSeconds(5); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); Console.WriteLine("Sending request"); var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/Get"); Console.WriteLine("Request sent. Awaiting response..."); if (Convert.ToInt32(response.StatusCode) == 200) { JObject menijson = JObject.Parse(await response.Content.ReadAsStringAsync()); JArray meniji = (JArray)menijson["items"]; List<Meni> list = meniji.ToObject<List<Meni>>(); foreach (Meni meni in list) { await meni.dodajjela(); } return list; } else { MessageBox.Show("Greška pri prustupanju popisa menija", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } } } private async Task dodajjela() { using (var handler = new LoggingHttpClientHandler()) using (var client = new HttpClient(handler)) { client.Timeout = TimeSpan.FromSeconds(5); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); Console.WriteLine("Sending request"); var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/GetRelated?men={this.id_menija}"); Console.WriteLine("Request sent. Awaiting response..."); if (Convert.ToInt32(response.StatusCode) == 200) { JObject jelajson = JObject.Parse(await response.Content.ReadAsStringAsync()); JArray jela = (JArray)jelajson["items"]; foreach (var jel in jela) { var resp = await Jela.Retrieve(jel["idjela"].ToString()); this.jelas.Add(resp); } } } } public static async Task<Jela> Retrieve(string id) { using (var handler = new LoggingHttpClientHandler()) using (var client = new HttpClient(handler)) { client.Timeout = TimeSpan.FromSeconds(5); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); Console.WriteLine("Sending request"); var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/Jelo?je={id}"); Console.WriteLine("Request sent. Awaiting response..."); if (Convert.ToInt32(response.StatusCode) == 200) { JObject jelajson = JObject.Parse(await response.Content.ReadAsStringAsync()); jelajson.Remove("links"); return CreateObj(jelajson); } return null; } } private static Jela CreateObj(JObject objekt) { Image slikajelo = null; using (WebClient client = new WebClient()) { byte[] slikabiteovi = client.DownloadData($"https://apex.oracle.com/pls/apex/student12345/Meni/image?br={objekt.Property("id_jelo").Value.ToString()}"); using (MemoryStream ms = new MemoryStream(slikabiteovi)) { slikajelo = Image.FromStream(ms); } } Jela jelo = new Jela { id_jela = objekt.Property("id_jelo").Value.ToString(), slika= slikajelo, ime_jela= objekt.Property("ime_jela").Value.ToString() }; return jelo; }
异常原因分析
- GDI+资源泄漏而非托管内存不足:异常来自
System.Drawing.dll,说明是非托管的GDI+资源耗尽,而非托管内存不足(这就是为什么托管内存显示37MB却报错)。CreateObj方法中,Image.FromStream(ms)创建的Image会依赖外部MemoryStream,当Stream被using释放后,Image的内部资源会处于不稳定状态;同时Jela对象的Image属性未被主动释放,大量加载后GDI+资源被耗尽。 - HttpClient重复创建:每个请求都新建HttpClient和Handler,会导致TCP连接池耗尽,额外消耗系统资源,间接加剧GDI+资源压力。
- 未优化图片加载:直接加载原图,若图片尺寸过大,会占用更多GDI+资源。
解决方法
1. 修复Image资源管理
修改CreateObj方法,复制Image以解除对Stream的依赖,同时让Jela类实现IDisposable接口释放Image资源:
private static Jela CreateObj(JObject objekt) { Image slikajelo = null; using (WebClient client = new WebClient()) { byte[] slikabiteovi = client.DownloadData($"https://apex.oracle.com/pls/apex/student12345/Meni/image?br={objekt.Property("id_jelo").Value.ToString()}"); using (MemoryStream ms = new MemoryStream(slikabiteovi)) { // 复制Image,避免依赖外部Stream using (var tempImage = Image.FromStream(ms)) { slikajelo = new Bitmap(tempImage); } } } Jela jelo = new Jela { id_jela = objekt.Property("id_jelo").Value.ToString(), slika = slikajelo, ime_jela = objekt.Property("ime_jela").Value.ToString() }; return jelo; } // Jela类实现IDisposable public class Jela : IDisposable { public string id_jela { get; set; } public Image slika { get; set; } public string ime_jela { get; set; } public List<Jela> jelas { get; set; } private bool _disposed = false; protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // 释放托管资源 slika?.Dispose(); } _disposed = true; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
使用Jela对象后,需调用jelo.Dispose()释放资源,或在循环中用using包裹:
foreach (var jel in jela) { using(var resp = await Jela.Retrieve(jel["idjela"].ToString())) { this.jelas.Add(resp); } }
2. 复用HttpClient实例
避免每次请求新建HttpClient,改为静态单例复用:
// 在对应类中定义静态HttpClient private static readonly HttpClient _httpClient; static YourClassName() // 替换为实际类名 { var handler = new LoggingHttpClientHandler(); _httpClient = new HttpClient(handler); _httpClient.Timeout = TimeSpan.FromSeconds(5); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }
之后在RetrieveAll、dodajjela、Retrieve方法中直接使用_httpClient,移除原有的using创建代码。
3. 优化图片加载(可选)
若图片尺寸过大,加载时进行缩放,减少GDI+资源占用:
using (var tempImage = Image.FromStream(ms)) { // 缩放到目标宽度,保持比例 int targetWidth = 200; float scale = (float)targetWidth / tempImage.Width; int targetHeight = (int)(tempImage.Height * scale); slikajelo = new Bitmap(tempImage, targetWidth, targetHeight); }
内容的提问来源于stack exchange,提问作者DorianK
相关产品推荐
相关产品推荐

