Java线程引起的内存泄露问题浅析

在实际的开发中我们经常会遇到需要使用线程的情况,我以前通常使用这两种方式

第一种,线程使用内部类的方式包裹在实际的控制器内部,如下:
    class A {
            class B extends Thread {
                  @Override
                  public void run() {

                        super.run();
                  }
            }
      }

第二种,线程使用外部类,控制器采用参数的方式传入,如下:

    class C {};
      class D extends Thread {
            private C ref;
            public D(C obj) {
                  ref = obj;
            }

            @Override
            public void run() {
                  super.run();
            }
      }
其实,这两种常见的方式都是有问题的。下面就让我们来分析分析。
第一种,我们知道内部类中是可以访问外部类的一切资源的,为什么?因为内部类中隐藏了一个指向外部类的指针(但是如果是静态类就没问题,因为静态类中不会有外部类的任何实例的指针),那么这就有一个问题,如果在B被回收之前,A是不会被回收的,而且这种情况是经常发生的,设想一下,如果A是一个运行在UI线程的类,比如说是一个listview的adapter类,而B是adapter类中负责通过网络请求图片的工作线程,那么由于联网需要的时间可能比较长,用户可能等待的不耐烦,推出了这个listview界面,跳转到别的界面去了,那么由于B没有运行完,那么B不可能被回收,那么B中的隐藏指针就仍然有效,那么意味着A还在被引用,那么A是不会被回收的,这时候如果A引用了大量的UI资源的话(这对于UI线程下运行的类来说是很可能的)那么会导致这些资源都不会被回收,这种情况下就可能会造成严重的内存泄露。
 
第二种的原理其实跟第一种是一样的,不过这种形式引用看的更明白一些,道理跟第一种一样。
 
那么,通常的作法是这样的
    class E extends Thread {
            private WeakReference<C> weakRef;
            public E(C obj) {
                  weakRef = new WeakReference<C>(obj);
            }
            
            @Override
            public void run() {
                  C obj = weakRef.get();
                  if (obj != null) doSomething();
                  super.run();
            }
      }
这种方式使用弱引用来保持对UI线程中类的引用,就可以把UI线程和工作线程中对象的生命周期有效的隔离开, 这样如果UI线程不存在了,工作线程也就可以知道不必去更新UI了。
 
下面给出一个例子
  1 package com.test.memtest;
  2 
  3 import java.lang.ref.WeakReference;
  4 import java.util.concurrent.TimeUnit;
  5 
  6 public class TestMain {
  7 
  8     class A {
  9         public void show() {
 10             System.out.println("Hello, I'm an instance of A");
 11         }
 12         
 13         public void run(int time) {
 14             new B(time).start();
 15         }
 16         
 17         class B extends Thread {
 18             private int count;
 19             
 20             public B(int count) {
 21                 this.count = count;
 22             }
 23             
 24             @Override
 25             public void run() {
 26                 for (int i = 0; i < count; i++) {
 27                     System.out.println(String.format("B has been running for %d seconds", i));
 28                     show();
 29                     try {
 30                         Thread.sleep(1000);
 31                     } catch (InterruptedException e) {
 32                         // TODO Auto-generated catch block
 33                         e.printStackTrace();
 34                     }
 35                 }
 36                 super.run();
 37             }
 38             
 39         }
 40     }
 41     
 42     class C {
 43         public void show() {
 44             System.out.println("Hey, I'm an instance of C");
 45         }
 46     };
 47     class D extends Thread {
 48         private C ref;
 49         public D(C obj) {
 50             ref = obj;
 51         }
 52         
 53         @Override
 54         public void run() {
 55             for (int i = 0; i < 10; i++) {
 56                 System.out.println(String.format("D has been running for %d seconds", i));
 57                 ref.show();
 58                 try {
 59                     TimeUnit.SECONDS.sleep(1);
 60                 } catch (InterruptedException e) {
 61                     // TODO Auto-generated catch block
 62                     e.printStackTrace();
 63                 }
 64             }
 65             super.run();
 66         }
 67     }
 68     
 69     class E extends Thread {
 70         private WeakReference<C> weakRef;
 71         public E(C obj) {
 72             weakRef = new WeakReference<C>(obj);
 73         }
 74         
 75         @Override
 76         public void run() {
 77             for (int i = 0; i < 10; i++) {
 78                 System.out.println(String.format("E has been running for %d seconds", i));
 79                 C ref = weakRef.get();
 80                 if (ref != null) {
 81                     ref.show();
 82                 } else {
 83                     System.out.println("C has been garbage collected");
 84                 }
 85                 try {
 86                     TimeUnit.SECONDS.sleep(1);
 87                 } catch (InterruptedException e) {
 88                     // TODO Auto-generated catch block
 89                     e.printStackTrace();
 90                 }
 91             }
 92             super.run();
 93         }
 94     }
 95     /**
 96      * @param args
 97      */
 98     public static void main(String[] args) {
 99 
100         System.out.println("main thread start!");
101         TestMain instance = new TestMain();
102         instance.new A().run(10);
103         instance.new D(instance.new C()).start();
104         instance.new E(instance.new C()).start();
105         System.gc();
106         System.out.println("main thread over");
107     }
108 
109 }
输出结果如下:
main thread start!
B has been running for 0 seconds
Hello, I'm an instance of A
D has been running for 0 seconds
Hey, I'm an instance of C
main thread over
E has been running for 0 seconds
C has been garbage collected
B has been running for 1 seconds
Hello, I'm an instance of A
D has been running for 1 seconds
Hey, I'm an instance of C
E has been running for 1 seconds
C has been garbage collected
B has been running for 2 seconds
Hello, I'm an instance of A
D has been running for 2 seconds
Hey, I'm an instance of C
E has been running for 2 seconds
C has been garbage collected
B has been running for 3 seconds
Hello, I'm an instance of A
D has been running for 3 seconds
Hey, I'm an instance of C
E has been running for 3 seconds
C has been garbage collected
B has been running for 4 seconds
Hello, I'm an instance of A
D has been running for 4 seconds
Hey, I'm an instance of C
E has been running for 4 seconds
C has been garbage collected
B has been running for 5 seconds
Hello, I'm an instance of A
D has been running for 5 seconds
Hey, I'm an instance of C
E has been running for 5 seconds
C has been garbage collected
B has been running for 6 seconds
Hello, I'm an instance of A
D has been running for 6 seconds
Hey, I'm an instance of C
E has been running for 6 seconds
C has been garbage collected
B has been running for 7 seconds
Hello, I'm an instance of A
D has been running for 7 seconds
Hey, I'm an instance of C
E has been running for 7 seconds
C has been garbage collected
B has been running for 8 seconds
Hello, I'm an instance of A
D has been running for 8 seconds
Hey, I'm an instance of C
E has been running for 8 seconds
C has been garbage collected
B has been running for 9 seconds
Hello, I'm an instance of A
D has been running for 9 seconds
Hey, I'm an instance of C
E has been running for 9 seconds
C has been garbage collected
 
我们可以很清楚的看出,在线程B,D运行期间,他们引用的对象都没有得到回收,而线程E引用的对象在gc后得到了回收。
补充,在线程中设置监听器的情况有一些麻烦的情况,留在下一篇文章中讨论。

 

posted @ 2012-11-04 23:01  岳昂  阅读(1619)  评论(0编辑  收藏  举报