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

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) {
            @Override
            protected Map<String, String> getParams() {
                return params;
            }

            @Override
            protected 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=1
   0:	aload_0
   1:	invokespecial	#2; //Method java/lang/Object."<init>":()V
   4:	aload_0
   5:	ldc	#3; //String 37785612
   7:	putfield	#1; //Field mName:Ljava/lang/String;
   10:	return
  LineNumberTable:
   line 1: 0
   line 2: 4
   line 3: 10

public void showDemo(java.lang.String);
  Code:
   Stack=4, Locals=2, Args_size=2
   0:	new	#4; //class Hello$1
   3:	dup
   4:	aload_0
   5:	aload_1
   6:	invokespecial	#5; //Method Hello$1."<init>":(LHello;Ljava/lang/String;)V
   9:	invokevirtual	#6; //Method Hello$1.show:()V
   12:	return
  LineNumberTable:
   line 8: 0
   line 14: 12

static java.lang.String access$000(Hello);
  Code:
   Stack=1, Locals=1, Args_size=1
   0:	aload_0
   1:	getfield	#1; //Field mName:Ljava/lang/String;
   4:	areturn
  LineNumberTable:
   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=3
   0:	aload_0
   1:	aload_1
   2:	putfield	#1; //Field this$0:LHello;
   5:	aload_0
   6:	aload_2
   7:	putfield	#2; //Field val$s:Ljava/lang/String;
   10:	aload_0
   11:	aload_1
   12:	invokespecial	#3; //Method Hello$Demo."<init>":(LHello;)V
   15:	return
  LineNumberTable:
   line 8: 0

public void show();
  Code:
   Stack=3, Locals=1, Args_size=1
   0:	getstatic	#4; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	new	#5; //class java/lang/StringBuilder
   6:	dup
   7:	invokespecial	#6; //Method java/lang/StringBuilder."<init>":()V
   10:	ldc	#7; //String s=
   12:	invokevirtual	#8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:	aload_0
   16:	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;)V
   28:	getstatic	#4; //Field java/lang/System.out:Ljava/io/PrintStream;
   31:	new	#5; //class java/lang/StringBuilder
   34:	dup
   35:	invokespecial	#6; //Method java/lang/StringBuilder."<init>":()V
   38:	ldc	#11; //String name=
   40:	invokevirtual	#8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   43:	aload_0
   44:	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;)V
   59:	return
  LineNumberTable:
   line 10: 0
   line 11: 28
   line 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_0
   44:	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对象不会被垃圾回收,从而产生导致内存泄漏。

时间: 2024-08-07 04:31:37

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

使用Xcode Instruments Leak解决内存泄漏问题

iOS 5.0之后apple引入了Xcode编译器特性ARC(Automatic Reference Counting,自动引用计数)来帮助开发者管理内存,但为了追求app的高性能与减少安装包大小,工作中很多时候需要我们手动管理内存.再牛的开发者也不能保证自己写的code 100%没有内存泄露,出现内存泄露不可怕,可怕的是我们时间与精力花了大把,但内存泄露依旧没解决,即影响了工作效率也影响自己的心情. 下面就讲解xcode中的内存调试神器---Instruments Leak ,不管是ios开发

捉虫记录:解决内存泄漏问题

LinJM   2014_05_23 解决内存泄漏问题 在VS2010的Debug模式下面,点击运行,然后退出,之后会在输出框里面出现内存泄漏信息(如下图所示). Analysis:主要是new了之后没有delete相应的变量,所以,很明显就是要在不使用时delete掉这个变量.不过,有个问题,如下图所示: 我代码修改位置如下所示: 我把红下划线部分注释掉就不会出现上面那个问题,后来讨论分析才发现pBim现在分配给了pAdjustmentLyInfo,二者现在指向同一个内存空间,当我delete

ES6通过WeakMap解决内存泄漏问题

一.Map 1.定义 Map对象保存键值对,类似于数据结构字典:与传统上的对象只能用字符串当键不同,Map对象可以使用任意值当键. 2.语法 new Map([iterable]) 属性 size:返回键值对的数量. 操作方法 set(key, value):设置(新增/更新)键key的值为value,返回Map对象. get(key):读取键key的值,没有则返回undefined. has(key):判断一个Map对象中是否存在某个键值对,返回true/false. delete(key):

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

https://www.cnblogs.com/zhaoyanjun/p/5981386.html 前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的作用. 本文的内存泄漏检测工具是:LeakCanary  github地址:https://github.com/square/leakcanary 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并

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

自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclipse,成为主流的Android开发IDE.Android Studio在为我们提供了良好的编码体验的同时,也提供了许多对App性能分析的工具,让开发者可以更方便分析App性能.Google在IO大会上一直告诫开发者不要无节制的使用手机内存,要注意一些不良的开发习惯会导致App的内存泄漏.虽然如今网上

使用ViewModel+Data Binding解决内存泄漏问题

1.在我们写APP的时候经常会遇到这种情况,点击一个按钮后,通过网络异步操作从后台获取信息,然后再展示到UI,例如 public class MyActivity extends BaseActivity { private void getUser(){ LoadingDialog loadingDialog=new LoadingDialog(this,"加载中"); loadingDialog.show(); Request request = new Request.Build

浅析Context及可能带来的内存泄漏问题

什么是 Context 纯英文含义来看,Context 意指上下文.环境.背景等等--那么 Android 中的 Context 的含义和这些英文释义有什么联系呢?不妨看看 Google 给出的定义: Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. I

非静态内部类创建静态实例造成的内存泄漏

请大家思考,为什么会内存泄漏? 1. 首先,非静态内部类默认会持有外部类的引用. 2. 然后又使用了该非静态内部类创建了一个静态的实例. 3. 该静态实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收. 正确的做法有两种,一种是将内部类testResource改成静态内部类,还有就是将testResource抽取出来,封装成一个单例,如上一个例子那样,但是需要context时单例要切记注意Context的泄漏,使用ap

Android开发常见的Activity中内存泄漏及解决办法

上一篇文章楼主提到由Context引发的内存泄漏,在这一篇文章里,我们来谈谈Android开发中常见的Activity内存泄漏及解决办法.本文将会以"为什么""怎么解决"的方式来介绍这几种内存泄漏. 在开篇之前,先来了解一下什么是内存泄漏. 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费. 怎