概述
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对象不会被垃圾回收,从而产生导致内存泄漏。最后
以上就是愉快奇异果为你收集整理的解决内存泄漏更加清楚的认识到Java匿名类与外部类的关系的全部内容,希望文章能够帮你解决解决内存泄漏更加清楚的认识到Java匿名类与外部类的关系所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复