1.事件起因

在做项目的时候,通过Android Studio的Memory Monitor窗口观察程序内存使用情况,发现当程序退出的时候,有一部分应该释放掉的内存没有释放掉,知道程序中应该有内存泄漏了。为了发现程序中的内存泄漏,我切换了IDE工具到Eclipse,里面安装了内存泄漏的分析工具MAT,具体怎么用MAT分析内存泄漏可以自己Google,我把我自己找到内存泄漏的地方贴出来
从上图中可以看到,有24M左右的内存被mView(其实它真正是一个Fragment)这个变量持有,导致Java垃圾回收的时候不会回收掉。追踪到最上面,GC Root的根是Volley库里面一个缓存对象mCacheQueue持有了mView,导致系统不会回收.发现了原因,解决起来就好办。解决方法有两个,一是清空Volle缓存对象,二是把mListener置空,不在有引用持有mView对象。

2.代码是怎么样让Volley 的缓存对象持有了mView对象呢?

关键性代码如下,删除了部分逻辑,只看匿名内部类部分
public CCHttpRequest(final String url,final Map<String, String> params,final CCApiCallback callback) {mRequest = new HttpStringRequest(HttpGsonRequest.Method.POST, url) {@Overrideprotected Map<String, String> getParams() {return params;}@Overrideprotected void onResponse(String s) {
<pre name="code" class="java">   <span style="white-space:pre">      </span>if (null != callback) {callback.onResponse(data.toString(), hasServerTime, serverTime);}

} @Override protected void onErrorResponse(Exception e) { if (null != callback) { //系统错误返回-1 callback.onError(createErrorMessage(-1, e.getMessage())); } } }; } 被Volley缓存持有的对象是new HttpStringRequest 这个匿名类对象的实例,为什么方法中的参数final CCApiCallback callback这个参数会被新创建出来的匿名内部内持有呢?

3.一个简单的例子解释java匿名类与外部类的关系

书写一个简单的Hello.java文件,里面包括了一个匿名类与一个内部类Demo

public class Hello{private String mName="37785612";class Demo{public void show(){}}public void showDemo(final String s){new Demo(){public void show(){System.out.println("s="+s);System.out.println("name="+mName);}}.show();}
}

执行javac Hello.java编译完成后,会在同一目录下生成如下几个class文件,Hello.class,Hello$1.class,Hello$Demo.class。Hello.class就是我们源文件Hello的类文件,Hello$1.class是在showDemo()方法里面new Demo()那个匿名类的类文件,Hello$Demo.class是内部类Demo的类文件,我们这里主要分析Hello.class与Hello$1.class.

执行命令 javap -v Hello,汇编出来的部分代码如下:
{
public Hello();Code:Stack=2, Locals=1, Args_size=10: aload_01:   invokespecial   #2; //Method java/lang/Object."<init>":()V4:    aload_05:   ldc #3; //String 377856127: putfield    #1; //Field mName:Ljava/lang/String;10: returnLineNumberTable: line 1: 0line 2: 4line 3: 10public void showDemo(java.lang.String);Code:Stack=4, Locals=2, Args_size=20:  new #4; //class Hello$13:   dup4:   aload_05:   aload_16:   invokespecial   #5; //Method Hello$1."<init>":(LHello;Ljava/lang/String;)V9:    invokevirtual   #6; //Method Hello$1.show:()V12:    returnLineNumberTable: line 8: 0line 14: 12static java.lang.String access$000(Hello);Code:Stack=1, Locals=1, Args_size=10:   aload_01:   getfield    #1; //Field mName:Ljava/lang/String;4:  areturnLineNumberTable: line 1: 0}

可以看到这里有一个方法access$000(Hello)是我们在源文件中没有出现的,而编译后会多了这个方法,它其实都是返回变量mName的值,后面会说到这个方法会被怎么用

继续执行命令javap -v Hello$1,汇编出来的部分代码如下

{
final java.lang.String val$s;final Hello this$0;Hello$1(Hello, java.lang.String);Code:Stack=2, Locals=3, Args_size=30:   aload_01:   aload_12:   putfield    #1; //Field this$0:LHello;5:    aload_06:   aload_27:   putfield    #2; //Field val$s:Ljava/lang/String;10: aload_011:  aload_112:  invokespecial   #3; //Method Hello$Demo."<init>":(LHello;)V15:  returnLineNumberTable: line 8: 0public void show();Code:Stack=3, Locals=1, Args_size=10: getstatic   #4; //Field java/lang/System.out:Ljava/io/PrintStream;3:    new #5; //class java/lang/StringBuilder6:   dup7:   invokespecial   #6; //Method java/lang/StringBuilder."<init>":()V10:    ldc #7; //String s=12: invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;15:    aload_016:  getfield    #2; //Field val$s:Ljava/lang/String;19: invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;22:    invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;25:   invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V28:  getstatic   #4; //Field java/lang/System.out:Ljava/io/PrintStream;31:   new #5; //class java/lang/StringBuilder34:  dup35:  invokespecial   #6; //Method java/lang/StringBuilder."<init>":()V38:    ldc #11; //String name=40: invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;43:    aload_044:  getfield    #1; //Field this$0:LHello;47:   invokestatic    #12; //Method Hello.access$000:(LHello;)Ljava/lang/String;50:   invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;53:    invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;56:   invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V59:  returnLineNumberTable: line 10: 0line 11: 28line 12: 59}

我们可以看到这个匿名类多了两个成员变量final java.lang.String val$s与final Hello this$0;在看下这个匿名类的构造函数Hello$1(Hello, java.lang.String);刚好是对两个成员变量进行赋值。this$0指向了外部类对象的引用,val$s指向了方法showDemo(final String s)的参数s所指向内存的引用。在看看匿名类是怎么访问外部类的成员变量呢?看下这几行汇编代码:

   43:  aload_044:  getfield    #1; //Field this$0:LHello;47:   invokestatic    #12; //Method Hello.access$000:(LHello;)Ljava/lang/String;

43,调用匿名内的this对象,44,取得匿名类的this$0成员变量,就是(Hello对象) 47 调用Hello的静态方法static java.lang.String access$000(Hello);获取成员mName的值

到这里就可以总结一下匿名内跟外部类的关系还有就是方法参数的关系:
       1.匿名类会有一个成员变量指向外部类的引用
       2.如果匿名类要使用方法中的某个参数,方法对应的参数必须是final的,这个好像是java强制规定的。并且会在匿名类中一个成员变量指向这个参数对象所指向的同一块存储区域
       3.匿名类访问外部类的成员是通过一个静态方法调用访问的,如果需要访问外部类的多个成员,就会在外部类中生成多个静态方法来提供给匿名类访问外部类的成员变量。

4.找出真正原因

从上面关于匿名类与外部类的关系理清之后,我们能够发现,我代码中的callback持有了一个外部对象,层层回退,最下面一个callback对象持有了一个外部引用,而刚好这个外部对象又持有了一个mListener对象,而mListener内部类对象又持有了一个外部对象,这个外部对象又持有了mView,导致程序退出时由于Volley的缓存不释放,mView对象不会被垃圾回收,从而产生导致内存泄漏。

解决内存泄漏更加清楚的认识到Java匿名类与外部类的关系相关推荐

  1. Android 如何有效的解决内存泄漏的问题

    前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的作用. 本文的内存泄漏检测工具是:LeakCana ...

  2. Android 性能优化 - 彻底解决内存泄漏

    起源 有趣的灵魂千奇百怪,内存泄漏的也是各式各样 我在15年写过一遍 文章 < android中常见的内存泄漏和解决办法>http://blog.csdn.net/wanghao20090 ...

  3. 内存(Display)、显示器(Monitor)和计算机(Computer)均属于一种产品(Product),其中计算机需要显示器和内存。请用Python语言简要实现这些类及它们之间的关系。

    内存(Display).显示器(Monitor)和计算机(Computer)均属于一种产品(Product),其中计算机需要显示器和内存.请用Python语言简要实现这些类及它们之间的关系. clas ...

  4. jstat 内存泄漏_基于Java内存dump文件分析解决内存泄漏问题

    概述 本文介绍一次解决现场java内存泄漏问题的经过,希望能提供后续遇到类似情况的读者一点思路. 生产环境发现的问题问题 生产环境运维人员反馈,服务器(windows系统)卡死,相关的服务都运行异常, ...

  5. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  6. android studio 解决内存泄漏

    自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclip ...

  7. 内存优化-使用tcmalloc分析解决内存泄漏和内存暴涨问题

    其实我一直很想写关于tcmalloc的内存泄漏检测的文章,只是一直记不起来该如何下笔,有时项目太忙,在整理这方便的思考过少,另外遇到的问题也不是很多,直到最近用tcmalloc帮A项目排查一些很棘手的 ...

  8. # 学习记录1(C#-解决内存泄漏的几种方法)

    这里写自定义目录标题 myImageCodecInfo = GetEncoderInfo("image/jpeg");myEncoder = Encoder.Quality;myE ...

  9. 解决内存泄漏导致的系统崩溃问题

    问题描述:某机器煲机爱奇艺视频2.3h后,会出现爱奇艺崩溃,或者退出视频播放界面,或者提示爱奇艺已停止运行. 同类现象还有系统静置4-5h,会出现闪屏现象,屏幕亮灭亮灭显示. 问题Debug: 客户提 ...

最新文章

  1. 满足极高读写性能需求的Key-Value数据库
  2. 小程序分享朋友圈之填坑模式
  3. python列表list的基本性质
  4. python从多层循环嵌套中退出_python中退出多层循环的方法
  5. (JAVA) * 使用正则表达式,给字符串排序 * 使用数组排序
  6. POJ_3090.Visible Lattice Points
  7. Log4J 日志的异步类解读(lAsyncAppender)
  8. 奇怪的等待事件“enq: ss - contention”
  9. sql语言的一大类 DML 数据的操纵语言
  10. 解读mpvue官方文档的Class 与 Style 绑定及不支持语法
  11. 【优化预测】基于matlab灰狼算法优化BP神经网络预测【含Matlab源码 1729期】
  12. R语言学习笔记 | R语言的入门
  13. 学生社团管理系统(Java+Swing+mysql)(超简陋)
  14. 工行u盾显示316_详解工行U盾及使用方法和注意事项
  15. app抓包工具_安卓APP逆向入门分析——破解某APP登陆请求参数
  16. JAVA生成64,32位UUID密钥
  17. C++17新属性详解
  18. 广东未来科技:书写立体显示事业传奇的行业独角兽
  19. Unity -- 用EasyAR制作出AR红包
  20. 神经网络怎么学,怎么学神经网络

热门文章

  1. R语言 | 批量修改变量类型(比如:把所有字符型变量转化为因子型)
  2. Xero 系列之库存管理篇
  3. Java 面试的“完美圣经”,有了这些还愁面试吗?
  4. CentOS 8 安装golang
  5. 在小公司熬了两年后我终于如愿以偿进了阿里
  6. FP5207B:带软启动工作频率可调DC-DC升压IC
  7. 第四代计算机期间开始采用了,在第四代计算机期间内计算机的应用逐步进入到.docx...
  8. android手机通讯录没了,手机联系人不见了怎么恢复?手机通讯录误删如何恢复...
  9. 佳博Gainscha GP-3150TIN 打印机驱动
  10. iOS 苹果开发者中文网站学习