您知道吗:未释放事件Handler可能导致内存泄漏

以前曾看见过这样一个问题:托管代码会不会导致内存泄漏。自己对GC的了解也不是很深,但还是比较赞成这样的观点:托管代码不会产生内存泄漏,除非你没有正确释放非托管资源。
今天看到一个非常有趣的例子,关于没有释放事件的Handler导致的内存泄漏。
以前对于释放Handler的观念是一点也没有,这主要因为没此方面的意识,没有养成好的习惯。只知道当关心这个事件的时候就注册一下, 暂时不关心了就移除掉。却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收,导致不必要的内存浪费。

事情是这样的,今天在看项目Source Code的时候发现一个有趣的字眼:"WeakEvent". 自己以前对WeakReference有点了解,所以就好奇地看看这是个啥玩意。
发现其是一种通过弱引用实现的Delegate。因为没有太多的注释,所有不知其为啥用此种方式来封装事件。于是顺手Google了一下,找到了一篇关于weak event的非常有意思的文章。
文章里提出了一个问题,场景如下:

UnRelease Event Handler
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;

namespace ConsoleApplication16
{
    
class DisplaySettingsListener
    
{
        
byte[] m_ExtraMemory = new byte[1000000];

        
public DisplaySettingsListener()
        
{
            SystemEvents.DisplaySettingsChanged 
+= new EventHandler(ehDisplaySettingsChanged);
        }


        
private void ehDisplaySettingsChanged(object sender, EventArgs e)
        
{
        }

    }


    
class Program
    
{
        
static void DisplayMemory()
        
{
            Console.WriteLine(
"Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        }


        
static void Main()
        
{
            DisplayMemory();
            Console.WriteLine();
            
for (int i = 0; i < 5; i++)
            
{
                Console.WriteLine(
"--- New Listener #{0} ---", i + 1);
                DisplaySettingsListener listener 
= new DisplaySettingsListener();
                listener 
= null;
                GC.Collect();

                DisplayMemory();
            }

            Console.Read();
        }


    }

}

运行的结果如下:

 image

虽然我们释放了对listener的引用,并且强制GC进行回收,但我们可以看到其内存占用量还是变大了,出乎了我的意料。
这就是该文作者指出的事件列表里保存的是一个强引用而非弱引用。虽然上面释放了listener变量对Listener实例的引用,但因为仍然在DisplaySettingsChanged事件列表里保存了对Listener实例的引用,导致Listener实例并不能被垃圾回收(有人引用,自然不会回收)。
那么接下来看看下面的代码:

Release Event Hanlder
    class DisplaySettingsListener : IDisposable
    
{
        
byte[] m_ExtraMemory = new byte[1000000];

        
public DisplaySettingsListener()
        
{
            SystemEvents.DisplaySettingsChanged 
+= new EventHandler(ehDisplaySettingsChanged);
        }


        
private void ehDisplaySettingsChanged(object sender, EventArgs e)
        
{
        }


        
IDisposable Members#region IDisposable Members

        
public void Dispose()
        
{
            SystemEvents.DisplaySettingsChanged 
-= new EventHandler(ehDisplaySettingsChanged);
        }


        
#endregion

    }


    
class Program
    
{
        
static void DisplayMemory()
        
{
            Console.WriteLine(
"Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        }


        
static void Main()
        
{
            DisplayMemory();
            Console.WriteLine();
            
for (int i = 0; i < 5; i++)
            
{
                Console.WriteLine(
"--- New Listener #{0} ---", i + 1);
                DisplaySettingsListener listener 
= new DisplaySettingsListener();
                listener.Dispose();
                listener 
= null;
                GC.Collect();

                DisplayMemory();
            }

            Console.Read();
        }


    }

运行结果如下:

image 
结果是不是正如您猜测的呢:)。已经成功地回收了listener实例。 不知为何从432944字节变到446980字节,哪位高手赐教一下啊:)
详情可以看原文    The Problem With Delegates
在后续的文章中作者类似文章开头提到的Weak Event来解决这个问题: Solving the Problem with Events: Weak Event Handlers

也许您觉得写这样的一个Weak Event没有必要或者显得麻烦,但您一定要记得及时地在必要的地方调用 -= 取消不再关心的事件。本文的目的也只是在此方面提个善意的提醒。

转载于:https://www.cnblogs.com/anders06/archive/2008/01/15/1040123.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
问题:Handler造成的内存泄漏 在Android开发中,Handler是用于处理消息队列和线程间通信的一种机制。然而,如果不正确地使用Handler,可能导致内存泄漏的问题。下面是一些可能导致Handler内存泄漏的情况: 1. 静态内部类持有外部类引用:当将Handler声明为静态内部类时,它会持有外部类的引用。这意味着即使Activity已经销毁,Handler仍然可以访问该Activity的实例,从而导致无法回收Activity的内存。 解决方法:将Handler声明为非静态内部类或匿名内部类。这样,Handler不会默认持有外部类的引用,而是通过传递参数的方式来获取对Activity的引用。 示例代码: ```java public class MyActivity extends Activity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // 处理消息 return false; } }); } @Override protected void onDestroy() { super.onDestroy(); // 在Activity销毁时清除消息队列中的消息,以防止内存泄漏 mHandler.removeCallbacksAndMessages(null); } } ``` 2. 在Activity中创建并持有Handler对象:如果在Activity中直接创建并持有一个Handler对象,而没有将其与特定的线程关联,那么这个Handler对象将成为GC Root,导致Activity无法被回收,从而引发内存泄漏。 解决方法:将Handler与特定的线程关联。可以通过调用Thread类的start()方法或者使用AsyncTask等异步任务类来实现。 示例代码: ```java public class MyActivity extends Activity { private Thread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mThread = new Thread(new MyRunnable()); mThread.start(); // 将Handler与线程关联,使其成为线程的运行对象 } private class MyRunnable implements Runnable { @Override public void run() { Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { // 在指定线程中处理消息,避免内存泄漏问题 } }; // 在此处执行其他操作... } } @Override protected void onDestroy() { super.onDestroy(); // 在Activity销毁时停止线程,确保资源及时释放,避免内存泄漏问题 mThread.interrupt(); // 通过中断线程来停止其执行,释放资源 } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值