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后得到了回收。
补充,在线程中设置监听器的情况有一些麻烦的情况,留在下一篇文章中讨论。