深入分析 Handler 内存泄露

2024-05-28 11:32

本文主要是介绍深入分析 Handler 内存泄露,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 1. 在 Activity 中直接使用 Handler 时候编译器警告内存泄漏
    • 1.1 Java
    • 1.2 kotlin
  • 2. 为什么 handler 会导致内存泄漏呢?
    • 2.1 什么是内存泄漏
    • 2.2 handler 引用路径分析
  • 3. 内部类为什么会持有外部类的引用
  • 4. kotlin中的内部类与Java有什么不一样吗
  • 5. solution sample code
    • 5.1 四种引用
    • 5.2 Java code
    • 5.3 kotlin code
  • 参考链接

1. 在 Activity 中直接使用 Handler 时候编译器警告内存泄漏

1.1 Java

public class SampleActivity extends Activity {private final Handler mLeakyHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// ... }};
}

在实际编写中,我们往往会得到如下警告:

In Android, Handler classes should be static or leaks might occur.

1.2 kotlin

//ListAdapter代码中有如下代码:
private val handler = object : Handler() {override fun handleMessage(msg: Message) {if (msg.what == COUNT_DOWN) {notifyItemChanged(msg.arg1, COUNT_DOWN_TIME)}}
}

在这里插入图片描述

编译后的 java 代码:

private final <undefinedtype> handler;this.handler = new Handler() {public void handleMessage(@NotNull Message msg) {Intrinsics.checkNotNullParameter(msg, "msg");if (msg.what == 1) {SecondHandCarOrderListAdapter.this.notifyItemChanged(msg.arg1, "countDownTime");}}};

2. 为什么 handler 会导致内存泄漏呢?

2.1 什么是内存泄漏

一句话: Activity 走完 onDestory 之后应当被销毁但是实际并没有,handler 持有 该 Activity 的引用导致它无法被销毁

Java 虚拟机中使用可达性分析的算法来决定对象是否可以被回收。即通过GCRoot 对象为起始点,向下搜索走过的路径(引用链),如果发现某个对象或者对象组为不可达状态,则将其进行回收。

内存泄漏指的就是有些对象(短周期对象)没有用了,但是却被其他有用的类(长周期对象)所引用,从而导致无用对象占据了内存空间,形成内存泄漏。

所以 Handler 内存泄漏 的问题只说 内部类持有了外部类的引用 是不完整的,没有指出内部类被谁所引用,那么按道理来说是不会发生内存泄漏的,因为内部类和外部类都是无用对象了,是可以被正常回收的。

所以为什么内部类 handler 无法被回收呢?以至于导致它持有的 Activity 也无法正常被回收

2.2 handler 引用路径分析

简单来说,HandlerActivity 发生了内存泄漏,从引用路径来看,是被匿名内部类的实例 mHandler 持有引用了,而 Handler 的引用是被 Message 持有了,Message 引用是被 MessageQueue 持有了…

//引用路径大致酱紫主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

在这里插入图片描述

  • 当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个 Looper 对象包含一个简单的消息队列 Message Queue, 并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。主线程的 Looper 对象会伴随该应用程序的整个生命周期

  • 然后,当主线程里,实例化一个 Handler 对象后,它就会自动与主线程Looper 的消息队列关联起来。所有发送到消息队列的消息 Message 都会拥有一个对 Handler 的引用,所以当 Looper来处理消息时,会据此回调 Handler#handleMessage(Message)

  • java 里,非静态内部类匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会

3. 内部类为什么会持有外部类的引用

这是因为内部类虽然和外部类写在同一个文件中,但是编译后还是会生成不同的class文件,其中内部类的构造函数中会传入外部类的实例,然后就可以通过 this$0 访问外部类的成员

eg:

//原代码
class InnerClassOutClass{class InnerUser {private int age = 20;}
}//class代码
class InnerClassOutClass$InnerUser {private int age;InnerClassOutClass$InnerUser(InnerClassOutClass var1) {this.this$0 = var1;this.age = 20;}
}

4. kotlin中的内部类与Java有什么不一样吗

在这处理一下 1.2 中存在的内存泄漏问题

这是因为在kotlin中的匿名内部类分为两种情况:

  • 在Kotlin中,匿名内部类如果没有使用到外部类的对象引用时候,是不会持有外部类的对象引用的,此时的匿名内部类其实就是个静态匿名内部类,也就不会发生内存泄漏。
  • 在Kotlin中,匿名内部类如果使用了对外部类的引用,像我刚才使用了btn2,这时候就会持有外部类的引用了,就会需要考虑内存泄漏的问题。
    所以我特意加了这一句,让匿名内部类持有外部类的引用,复现内存泄漏问题。

同样 kotlin 中对于内部类也是和 Java 有区别的:

  • Kotlin 中所有的内部类都是默认静态的,也就都是静态内部类。
  • 如果需要调用外部的对象方法,就需要用 inner 修饰,改成和Java一样的内部类,并且会持有外部类的引用,需要考虑内存泄漏问题。
    • 用了 inner 关键字就和 Java 内部类一样用法了

5. solution sample code

5.1 四种引用

  • 强引用就是对象被强引用后,无论如何都不会被回收。
  • 弱引用就是在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
  • 软引用就是在系统将发生内存溢出的时候,回进行回收。
  • 虚引用是对象完全不会对其生存时间构成影响,也无法通过虚引用来获取对象实例,用的比较少

5.2 Java code

public class SampleActivity extends Activity {/*** Instances of static inner classes do not hold an implicit* reference to their outer class.*/private static class MyHandler extends Handler {private final WeakReference<SampleActivity> mActivity;public MyHandler(SampleActivity activity) {mActivity = new WeakReference<SampleActivity>(activity);}@Overridepublic void handleMessage(Message msg) {SampleActivity activity = mActivity.get();if (activity != null) {// ...}}}private final MyHandler mHandler = new MyHandler(this);/*** Instances of anonymous classes do not hold an implicit* reference to their outer class when they are "static".*/private static final Runnable sRunnable = new Runnable() {@Overridepublic void run() { /* ... */ }};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Post a message and delay its execution for 10 minutes.mHandler.postDelayed(sRunnable, 1000 * 60 * 10);// Go back to the previous Activity.finish();}
}

5.3 kotlin code

private val handler = MyHandler(WeakReference(this))class MyHandler(var adapter: WeakReference<SecondHandCarOrderListAdapter>):Handler(){override fun handleMessage(msg: Message) {super.handleMessage(msg)if (msg.what == COUNT_DOWN) {adapter.get()?.notifyItemChanged(msg.arg1, COUNT_DOWN_TIME)}}}fun destroy() {handler?.removeCallbacksAndMessages(null)timer?.cancel()timer?.purge()timer = nulltask = null}

参考链接

  • How to Leak a Context: Handlers & Inner Classes
  • Handler内存泄露全面分析

这篇关于深入分析 Handler 内存泄露的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1010444

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP

PHP原理之内存管理中难懂的几个点

PHP的内存管理, 分为俩大部分, 第一部分是PHP自身的内存管理, 这部分主要的内容就是引用计数, 写时复制, 等等面向应用的层面的管理. 而第二部分就是今天我要介绍的, zend_alloc中描写的关于PHP自身的内存管理, 包括它是如何管理可用内存, 如何分配内存等. 另外, 为什么要写这个呢, 因为之前并没有任何资料来介绍PHP内存管理中使用的策略, 数据结构, 或者算法. 而在我们

string字符会调用new分配堆内存吗

gcc的string默认大小是32个字节,字符串小于等于15直接保存在栈上,超过之后才会使用new分配。

PHP内存泄漏问题解析

内存泄漏 内存泄漏指的是在程序运行过程中申请了内存,但是在使用完成后没有及时释放的现象, 对于普通运行时间较短的程序来说可能问题不会那么明显,但是对于长时间运行的程序, 比如Web服务器,后台进程等就比较明显了,随着系统运行占用的内存会持续上升, 可能会因为占用内存过高而崩溃,或被系统杀掉 PHP的内存泄漏 PHP属于高级语言,语言级别并没有内存的概念,在使用过程中完全不需要主动申请或释放内

C++学习笔记----6、内存管理(四)---- 通常的内存陷阱(2)

3、Windows环境下使用Visual C++发现并修复内存渗露         内存渗露很难跟踪是因为你无法很容易地看着内存并且看到什么对象处于使用中,一开始在哪儿分配的内存。然而,是有程序可以为你做到这一点的。内存渗露检测工具有昂贵的专业软件包,也有免费下载的工具。如果你是在Microsoft Visual C++环境下工作,它的排错工具库有内建的对于内存渗露检测的支持。该内存检测默认没有

控制台和MFC中内存泄露工具vld的使用

最近想检测下项目中内存泄露的情况,选中了vld这款。在查找使用方法的时候,大都是控制台下的示例,添加到main函数所在的源文件上。换成MFC就纠结了,不知道添加到哪里去。本文记录控制台和MFC中的使用vld过程。    vld资源:    1)、大家可以移步下边的网址下载:     http://vld.codeplex.com/releases/view/82311    2