Android 从java字节码告诉你 为什么Handler会造成内存泄露

时间:2022-08-24 19:51:07

很多人面试的时候,都知道Handler 极易造成内存泄露,但是有一些讲不出来为什么,好一点的 会告诉你looper msg 之类的,但是你再往下问 为什么msg持有handler handler为什么

持有activity'的引用的时候 他们就答不出来了。这里我通过几个简单的例子 和极少部分的源码 来帮助大家彻底理解这一个流程。

那首先 我们来看一个例子,首先定义1个外部类 一个内部类:

 package com.test.zj;

 public class OuterClass {

     private int outerValue = 7;
private String outerName = "outer"; class InnerClass {
public void printOuterValue() {
System.out.println(outerName + ": " + outerValue);
}
} }

然后看一下我们的主类:

 package com.test.zj;

 public class MainClass {

     public static void main(String[] args) {
// TODO Auto-generated method stub
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.printOuterValue();
} }

这个例子相信经常写android 代码的人是不会陌生的。经常会写类似的代码。那这里有没有人思考过 Outer的那2个属性 不是private的吗,为什么内部类能直接用他们呢?

看一下字节码,首先我们看Outer的:

Android  从java字节码告诉你 为什么Handler会造成内存泄露

所以你看这里,大家一定很奇怪,我的outer 明明只有一个构造方法啊,这里怎么多了一个access0 access1 这是什么鬼。但是继续看 发现这2个方法 一个返回string 一个返回I 也就是int,似乎我们又明白了点什么

好继续看我们的内部类:

Android  从java字节码告诉你 为什么Handler会造成内存泄露

注意看内部类的print方法里的字节码,重要的地方我标红了,你看,原来outer里的那2个access方法是在这里被调用的。再看那个 this$0  看下冒号后面的内容 就能明白

这个 this$0就是指向外部类的指针啊! 所以 一个大家熟悉的概念 原理就在这了:内部类 持有外部类的引用。

然后有人又会说了,静态内部类不会持有外部类的引用啊。好,我们现在修改一下代码 看看是否是如此:

 package com.test.zj;

 public class OuterClass {

     private static int outerValue = 7;
private static String outerName = "outer"; static class InnerClass {
public void printOuterValue() {
System.out.println(outerName + ": " + outerValue);
}
} }
 package com.test.zj;

 import com.test.zj.OuterClass.InnerClass;

 public class MainClass {

     public static void main(String[] args) {
// TODO Auto-generated method stub
InnerClass innerClass = new InnerClass();
innerClass.printOuterValue();
} }

然后看下 字节码:

Android  从java字节码告诉你 为什么Handler会造成内存泄露

然后看下内部类的字节码:

Android  从java字节码告诉你 为什么Handler会造成内存泄露

你看很明显的 我们就能看到 在内部类的printf方法里  再调用外部类的属性的时候 就看不到 this0 这个指向外部类的指针了。

回到我们的handler ,我们这个时候 就能清晰的分析出 为什么handler 有的时候会造成内存泄露了。

 public class MainActivity extends MainActivity{

     private Handler mHandler=new Handler()
{
public void handleMessage(Message msg)
{ }
} protected void onCreate(Bundle saveInstance)
{
super.onCreate(saveInstance) mHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ } }, 1000 * 60 * 5); finish(); } }

我们来看看 这段代码为什么会造成内存泄露。

首先 我们得明白一点,当一个app被启动的时候,android 会帮我们创建一个供ui线程使用的消息队列Looper。这个Looper就是用来处理ui线程上的事件的,

比如什么点击事件啊,或者是我们android里面生命周期的方法啊 之类的。Looper是一条一条处理的。可不是一次性处理多条哦。可以看一下大概的源码:

  public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity(); for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
} // This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} msg.target.dispatchMessage(msg); if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
} // Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
} msg.recycleUnchecked();
}
}

一目了然 是一个循环,无限制的永远取messagequee对吧,取出来的是什么呢,废话当然是message。看27行。message对象里面有个target。

查看message源码得知:

 /*package*/ Handler target;

所谓target就是一个handler对吧。那么问题就来了,我们上面的sample代码里面 我们这个handler对象是什么啊?是一个内部类构造的对象。

这个内部类构造的对象持有了外部类Activity的引用!所以导致 activity 无法被真正释放掉。同样的那个runnable对象实际上也是一个内部类对象,

他也会持有activity的引用了。

内存泄露就是在这里发生的。

当然了,更改的方法也很简单,那就是直接把这个内部类 改成静态的不就行了!

 private static class MyHandler extends Handler
{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
} private MyHandler myHandler=new MyHandler();

那,又有人要问了,你这个静态类,不讲道理啊,我要引用activity的属性 比如textview 啥的,引用不了啊,总不能textview 还让我弄成static变量把,

那其实这边还是有解决方法的。

 private  TextView tv;
private static class MyHandler extends Handler
{
private final WeakReference<MainActivity> mActivity;
@Override
public void handleMessage(Message msg) { MainActivity activity=mActivity.get();
if (null!=activity)
{
activity.tv.setTag("123");
super.handleMessage(msg);
}
} public MyHandler(MainActivity mainActivity)
{
this.mActivity=new WeakReference<MainActivity>(mainActivity);
}
} private MyHandler myHandler=new MyHandler(MainActivity.this);

为什么要用弱引用,我解释一下,当我们activity被弹出栈以后,此时就没有强引用去指向这个activity对象了。

如果发生gc,这个activity就会被回收,activity持有的那些资源 也自然而然就烟消云散了。对于dalivk虚拟机来说

第一次gc 的时候 是会把 没有任何引用的对象 和 只有弱引用的对象全部回收掉的,只有当发现这2种对象全部回收掉以后

所剩下的内存依然不够,那此时就会再进行一次gc,这时候gc 会把软引用指向的对象也回收掉。所以这里用弱引用

是最合适的。

你看 如果handler不做这种处理的话,我们gc的时候,一看,诶,怎么还有一个对象(handler的对象)持有activity的引用,恩

还是不销毁了。。。所以就内存泄露了。

Android 从java字节码告诉你 为什么Handler会造成内存泄露的更多相关文章

  1. 大话&plus;图说:Java字节码指令——只为让你懂

    前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...

  2. 掌握Java字节码&lpar;转&rpar;

    Java是一门设计为运行于虚拟机之上的编程语言,因此它需要一次编译,处处运行(当然也是一次编写,处处测试).因此,安装到你系统上的JVM是原生的程序,而运行在它之上的代码是平台无关的.Java字节码就 ...

  3. Java字节码操纵框架ASM小试

    本文主要内容: ASM是什么 JVM指令 Java字节码文件 ASM编程模型 ASM示例 参考资料汇总 JVM详细指令 ASM是什么 ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既 ...

  4. Java字节码基础&lbrack;转&rsqb;

    原文链接:http://it.deepinmind.com/jvm/2014/05/24/mastering-java-bytecode.html Java是一门设计为运行于虚拟机之上的编程语言,因此 ...

  5. 在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  6. JAVA字节码解析

    Java字节码指令 Java 字节码指令及javap 使用说明 ### java字节码指令列表 字节码 助记符 指令含义 0x00 nop 什么都不做 0x01 aconst_null 将null推送 ...

  7. 【转】在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  8. Java字节码(&period;class文件)格式详解(一)

    原文链接:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html 小介:去年在读<深入解析JVM>的时候写的,记得当时还 ...

  9. 通过Java字节码发现有趣的内幕之String篇(上)(转)

    原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知 ...

随机推荐

  1. 一个神奇的POS -扫描 现场销售 开单打印票据 安卓物联网POS机 手持开单终端机 省时省力 高效准确!!

    5寸高清彩屏,高端大气上档次,小巧轻便,独特的包胶防护,坚固耐用,外形精细,美观!与软件灵活对接,解决企业手工盘点,手工输单,库存管理等困难,提高准确率,提高工作效率!! 应用领域:适用于仓库.超市. ...

  2. cocos2dx 公告效果

    第一种方法: http://blog.csdn.net/jackystudio/article/details/12991977 [玩转cocos2d-x之十六]滚动字幕和公告 第二种方法: http ...

  3. sql server 2000的安装

    一.安装sql 二.启动sql 三.查看sql版本 RTM版本,需要打补丁 四.安装SP4

  4. 分布式ID生成策略

    策略一.UUID 策略二.数据库自增序列 策略三.snowflake算法 策略四.基于redis自增 思路:利用增长计数API,业务系统在自增长的基础上,配合其他信息组装成为一个唯一ID. redis ...

  5. Pandas学习1 --- 数据载入

    import numpy as np import pandas as pd 数据加载 首先,我们需要将收集的数据加载到内存中,才能进行进一步的操作.pandas提供了非常多的读取数据的函数,分别应用 ...

  6. pandas 实现通达信里的MFI

    pandas 实现通达信里的MFI 算法里的关键点: combine()和rolling().sum()方法 combine -- 综合运算, rolling().sum() -- 滚动求和 利用pd ...

  7. oracle EBS上传和下载文件(转)

    最近一直在做一个工作流的项目,最终用户要求在发送消息的时候可以附带附件,这个又是给我的一个难题.在网上查了一下ORACLE上传资料,找到了黄建华前辈写的<Oracle EBS Forms开发指南 ...

  8. HDF5 文件格式简介

    三代测序下机的原始数据不再是fastq格式了,而是换成了hdf5 格式,在做三代数据的分析之前,有必要先搞清楚hdf5 这种文件格式; 官网的链接如下:https://support.hdfgroup ...

  9. HBase-集群状态信息

    代码如下 package com.hbase.HBaseAdmin; import java.io.IOException; import java.util.Collection; import j ...

  10. UML总结:UML用于建模描述结构和行为

    UML有3种基本的构造块:组件.关系和图 我们将 UML 中的图分为两大类: 结构图 行为图 (1)结构建模: 结构建模具有捕捉静态的功能,包括下列各项: 类图 对象图 组件图 部署图 结构模型代表的 ...