首页 > 系统相关 >避坑:.NET内存泄露的几种情况

避坑:.NET内存泄露的几种情况

时间:2023-07-03 12:56:15浏览次数:41  
标签:释放 对象 Dispose 避坑 内存 NET public 引用

内存“泄露”是开发中常见的问题之一,它会导致应用程序占用越来越多的内存资源,最终可能导致系统性能下降甚至崩溃。软件开发者需要了解在程序中出现内存泄露的情况,以避免软件出现该的问题。

什么是内存“泄露”?

内存泄露是申请了内存空间的变量一直在占用,无法释放。比如申请了一块内存空间,没有回收一直占用,直到最后内存溢出。

在.NET应用程序中,可能会出现以下几种情况导致内存泄漏。

1、 对象保持的引用过长

  • 情况:某个对象持有对其他对象的引用,并且该引用没有被正确释放。

  • 示例:一个长时间运行的任务中,持有对大量对象的引用,但任务执行完毕后未释放这些对象的引用。

  • 解决方案:在不再需要对象时,及时释放对其的引用。确保在任务完成后,所有不再需要的对象都被正确释放。

public class LongRunningTask
{
    private List<object> objects; // 对象列表

    public LongRunningTask()
    {
        objects = new List<object>();
    }

    public void RunTask()
    {
        // 执行长时间运行的任务
        // 将对象添加到列表中
        objects.Add(new object());
        objects.Add(new object());
        // ...
    }
    //用完后用这个方法释放对象列表
    public void Cleanup()
    {
        // 在任务完成后清理对象列表
        objects.Clear();
        objects = null; // 释放对列表对象的引用
    }
}

在上述示例中,LongRunningTask 类代表一个长时间运行的任务,它持有对一些对象的引用。在任务完成后,通过调用 Cleanup() 方法释放对对象列表的引用,从而允许垃圾回收器回收这些对象。

2、 事件处理未正确解注册

  • 情况:在应用程序中订阅了事件,但没有在不再需要时正确解注册。

  • 示例:一个对象订阅了另一个对象的事件,但在对象不再需要时忘记解注册事件。

  • 解决方案:在不再需要订阅事件时,确保正确解注册事件。可以在对象的生命周期结束时,手动调用事件的解注册方法或使用弱事件模式,以避免事件发布者持有订阅者的引用。

public class EventPublisher
{
    public event EventHandler SomeEvent;

    public void PublishEvent()
    {
        // 发布事件
        SomeEvent?.Invoke(this, EventArgs.Empty);
    }

    public void UnsubscribeEvent(EventHandler handler)
    {
        // 解注册事件处理程序
        SomeEvent -= handler;
    }
}

public class EventSubscriber
{
    private EventPublisher publisher;

    public EventSubscriber(EventPublisher publisher)
    {
        this.publisher = publisher;
        // 订阅事件
        publisher.SomeEvent += HandleEvent;
    }

    private void HandleEvent(object sender, EventArgs e)
    {
        // 处理事件
    }

    public void UnsubscribeFromEvent()
    {
        // 解注册事件处理程序
        publisher.UnsubscribeEvent(HandleEvent);
    }
}

在上述示例中,EventPublisher 类发布了一个事件 SomeEvent,EventSubscriber 类订阅了该事件。通过调用 UnsubscribeFromEvent() 方法,解注册事件处理程序,从而释放对事件发布者的引用。

3、长时间运行的后台任务:

  • 情况:应用程序中存在长时间运行的后台任务,这些任务持有对其他对象的引用,并且这些引用没有被正确释放。

  • 示例:一个后台线程持续运行并持有对大量对象的引用,但这些对象在任务完成后不再需要。

  • 解决方案:在后台任务完成后,及时释放对其他对象的引用。可以通过在任务执行完毕后手动解除引用,或使用异步编程模型,确保任务完成后自动释放引用。

public class BackgroundTask
{
    private CancellationTokenSource cancellationTokenSource;

    public void StartTask()
    {
        cancellationTokenSource = new CancellationTokenSource();

        Task.Run(() =>
        {
            // 长时间运行的后台任务
            while (!cancellationTokenSource.Token.IsCancellationRequested)
            {
                // 执行任务逻辑
            }
        }, cancellationTokenSource.Token);
    }

    public void StopTask()
    {
        cancellationTokenSource?.Cancel();
        cancellationTokenSource?.Dispose();
        cancellationTokenSource = null; // 释放对 CancellationTokenSource 对象的引用
    }
}

在上述示例中,BackgroundTask 类代表一个长时间运行的后台任务。通过调用 StartTask() 方法启动任务,并在适当的时候调用 StopTask() 方法停止任务。在停止任务时,通过取消 CancellationTokenSource 对象来结束任务,并释放对该对象的引用。

4、大对象没有被正确释放

  • 情况:大对象(如大型数组、大型集合等)占用大量内存,但在不再需要时没有被正确释放。

  • 示例:一个应用程序在运行过程中创建了大量大型对象,但这些对象在使用后未被正确释放。

  • 解决方案:在使用完大对象后,及时释放不再需要的部分或整个对象。可以使用`Dispose`方法或使用`using`语句来确保资源的正确释放。

public void ProcessLargeData()
{
    byte[] largeData = new byte[100000000]; // 创建一个大型数组
    // 处理大型数据
    // ...

    // 使用完大型数组后,及时释放
    largeData = null;
}

在上述示例中,创建了一个大型数组 largeData 来存储大量数据。在处理完数据后,通过将 largeData 设置为 null,释放对大型数组的引用,从而允许垃圾回收器回收该数组所占用的内存。

5、 不正确使用IDisposable接口

  • 情况:在使用实现了IDisposable接口的对象时,没有正确调用`Dispose`方法来释放资源。

  • 示例:一个对象实现了IDisposable接口,但在使用完对象后忘记调用`Dispose`方法。

  • 解决方案:在使用完实现了IDisposable接口的对象后,使用`using`语句或手动调用`Dispose`方法来释放资源。确保正确地管理实现了IDisposable接口的对象。

public class CustomResource : IDisposable
{
    private bool disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // 释放托管资源
            }

            // 释放非托管资源
            // ...

            disposed = true;
        }
    }

    ~CustomResource()
    {
        Dispose(false);
    }
}
//欢迎关注公众号:DOTNET开发跳槽,领取海量面试题。加微信号xbhpnet入群交流

在上述示例中,CustomResource 类实现了 IDisposable 接口。在 Dispose() 方法中,通过调用 Dispose(true) 来释放托管资源,通过调用 Dispose(false) 来释放非托管资源。在 CustomResource 类的析构函数中,调用 Dispose(false) 来确保资源的释放。使用时,应该在不再需要 CustomResource 对象时调用 Dispose() 方法,或使用 using 语句来自动释放资源。

结语

请注意,以上示例仅用于说明可能的内存泄漏情况和解决方案,并不一定适用于所有具体的应用程序。在实际开发中,应根据应用程序的特性和需求,仔细审查代码并确保正确的资源管理和释放,以避免内存泄漏问题的出现。

以上只列举了几种情况,还有其它情况,比如在代码中使用了静态变量也容易导致内存泄露。希望本文对你有所收获,欢迎留言和吐槽。

版权声明:本文来源于网友收集或网友供稿,仅供学习交流之用,如果有侵权,请转告小编或者留言,本公众号立即删除。


来源公众号:DotNet开发跳槽

标签:释放,对象,Dispose,避坑,内存,NET,public,引用
From: https://www.cnblogs.com/xbhp/p/17522450.html

相关文章

  • OutOfMemoryError: Java heap space/GC overhead limit exceeded 内存溢出问题排查
    一、背景我开发的给产线使用的工具时不时就无法登录,查看日志基本上都是内存溢出,查看实际内存基本上都占满了JVM设置的内存大小导致的现象就是SpringBoot项目无法登录,导致系统不可用。下面是我的java启动设置。javaw-Xmx6G-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPat......
  • java对象的内存布局
    写java时不管是我们自己new对象还是spring管理bean,尽管我们天天跟对象打交道,那么对象的结构和内存布局有多少人知道呢,这篇文章可带你入门,了解java对象内存布局。本文涉及到JVM指针压缩的知识点,不熟悉的小伙伴可以看前面写过的一篇关于指针压缩的文章。JVM之指针压缩首先说明,本......
  • x64 架构,也称作AMD64或Intel 64,是指一种64位的处理器架构,是对x86架构的扩展和升级。x6
    x64架构,也称作AMD64或Intel64,是指一种64位的处理器架构,是对x86架构的扩展和升级。x64架构支持更大的内存寻址范围和更高的性能,适用于运行64位操作系统和应用程序。x64架构最早由AMD引入,并在2003年取得了广泛的市场认可。随后,Intel也推出了兼容x64架构的处理器。目前,绝大多数......
  • Redis详解——内存数据库
    前言在Redis详解——存储中介绍了Redis的基础数据结构,本文我们来看看Redis是如何组织这些数据类型,来构建一个内存数据库的。一、内存数据库以下是Redis数据库的结构:Redis服务器程序所有的数据库都保存在redisService结构体中,其中有个db数组,为redisDb类型,每个元素为一个数据......
  • 【.Net/C#之ChatGPT开发系列】四、ChatGPT多KEY动态轮询,自动删除无效KEY
    ChatGPT是一种基于Token数量计费的语言模型,它可以生成高质量的文本。然而,每个新账号只有一个有限的初始配额,用完后就需要付费才能继续使用。为此,我们可能存在使用多KEY的情况,并在每个KEY达到额度上限后,自动将其删除。那么,我们应该如何实现这个功能呢?还请大家扫个小关。......
  • ASP.NET Core SignalR 入门
    本章将和大家分享使用SignalR生成实时应用的基础知识。通过本文您将学习如何:使用ASP.NETCoreSignalR+MVC+Vue2.x+require最终创建一个正常运行的简易聊天应用。废话不多说,我们直接来看一个Demo,Demo的目录结构如下所示:本Demo的Web项目为ASP.NETCoreWeb应用程序(目......
  • .net core读取配置文件
    先添加这两个开发包: 这是配置文件; {"Logging":{"LogLevel":{"Default":"Information","Microsoft.AspNetCore":"Warning"}},"AllowedHosts":"*","......
  • dotnet-微服务学习-dotnet集成SkyWaking链路追踪
    关于链路追踪的原来我们单独开一篇文章讲解这里我们来讲解SkyWaking的安装和集成 首先进入SkyWaking官网下载最新的包网址如下: https://skywalking.apache.org/downloads/ 1.1windows安装下载后Winwos直接运行双击bin目录下的startup.bat即可 注意 SkyWalk......
  • CANopen转ProfiNet网关在大跨径门机起重设备同步纠偏控制应用案例
     大型门机起重设备纠偏控制系统采用CanOpen通讯协议,而PLC使用的是ProfiNet协议,看似不兼容的两种协议如何实现互通?今天我们来看一下这个案例。通过捷米特JM-COP-PN设置纠偏系统的参数,同时采集门机左右双轨的轮子多点同步控制,速度、位置等信息。在经过简单的配置后,用户可以很轻......
  • Silverlight4中用net.tcp双工方式进行通信
     先简单说一下,为了更好地实现双向通信,.NETFramework在3.0的时候引入了一个全新的通信协议Net.TCP并作为WCF的一部分。现在Net.TCP将包含在Silverlight4中,相比于HTTPPollingDuplex,它极大地改进了吞吐量和连接的数量。我们可以通过Silverlight4绑定到HTTPDuplexServices上,......