先说为什么要进行内存泄漏检查,也就是内存泄漏会造成什么后果?JAVA是垃圾回收语言的一种,开发者无需特意管理内存分配。但是JAVA中还是存在着许多内存泄露的可能性,如果不好好处理内存泄露,会导致APP内存单元无法释放被浪费掉,导致使用者感觉越用越卡,最终导致内存全部占据堆栈(heap)挤爆进而程序崩溃。先看几张巴枪上的内存泄漏的图片。
随便就是100多K,3百多个溢出,虽然很多是重复测试造成的,但确实不可小视。
想要了解内存泄露,对内存的了解必不可少。
JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。
栈(stack):是简单的数据结构,但在计算机中使用广泛。栈最显著的特征是:LIFO(Last In, First Out, 后进先出)。比如我们往箱子里面放衣服,先放入的在最下方,只有拿出后来放入的才能拿到下方的衣服。栈中只存放基本类型变量和对象的引用(不是对象),常量。
堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
内存的概念大概理解清楚后,要考虑的问题来了:
到底是哪里的内存会让我们造成内存泄露?
在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中。
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。
综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收,但如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因!
垃圾回收机制
垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。
实现思想:我们将栈定义为root,遍历栈中所有的对象的引用,再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。
垃圾回收实现思想如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
引用类型
在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象
的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
Java/Android引用类型及其使用分析
1. 强引用(Strong reference)
实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机器开始考虑可能要对此内存进行的垃圾回收。如当进行编码:a = null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将被垃圾回收。
2. 软引用(Soft Reference)
软引用的一般使用形式如下:
A a = new A();
SoftReference srA = new SoftReference(a);
软引用所指示的对象进行垃圾回收需要满足如下两个条件:
1.当其指示的对象没有任何强引用对象指向它;
2.当虚拟机内存不足时。
因此,SoftReference变相的延长了其指示对象占据堆内存的时间,直到虚拟机内存不足时垃圾回收器才回收此堆内存空间。
3. 弱引用(Weak Reference)
同样的,弱引用的一般使用形式如下:
A a = new A();
WeakReference wrA = new WeakReference(a);
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。
内存泄露原因如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。
内存泄露的真因是:持有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!
其实在Android中会造成内存泄露的情景无外乎两种:
全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。活在Activity生命周期之外的线程。没有清空对Activity的强引用。
检查一下你的项目中是否有以下几种情况:Static Activities,Static Views,inner Classes,Anonymous Classes,Sensor Manager,TimerTask,Threads,动态广播注册了要注销,context跟ApplicationContext选择(能用ApplicationContext就不用context),第3方SDK要关闭,Context,单例的使用,其中TimerTask,Threads也属于内部类或是匿名内部类。
关于leakCanary的集成很简单见https://github.com/square/leakcanary。原理的话见http://www.jianshu.com/p/5ee6b471970e,不是我们分析的重点,当然也可以使用MAT工具来分析,不过比较麻烦http://blog.csdn.net/aaa2832/article/details/19419679,下面是由leakCanary内存泄漏检测工具检测出来的项目当中的一些例子。
Static Activities
1.以前: public class LogUtils {
.....
public static Context sContext;
.....
public static void init(Context context) {
weakRefCtx = new WeakReference
if (weakRefCtx.get()==null)
return;
sContext = weakRefCtx.get();
.....
String dir = sContext.getCacheDir().toString() + "/Logs/";
.....
public class BaseActivity extends Activity implements CheckUpdateBack {
protected void onCreate(Bundle savedInstanceState) {
L ogUtils.init(this);
现在:1.public class BaseActivity extends Activity implements CheckUpdateBack {
protected void onDestroy() {
super.onDestroy();
LogUtils.sContext = null;
以前:2.public class CustomProgressDialog extends Dialog {
private Context context = null;
private static CustomProgressDialog customProgressDialog = null;
现在:2.public class CustomProgressDialog extends Dialog {
private Context context = null;
private static CustomProgressDialog customProgressDialog = null;
public static void CancelCustomProgressDialog(){
customProgressDialog.cancel();
customProgressDialog = null ;
}
以前:3.public class ToastUtil {
private static Toast toast = null;
public static void toastText(Context context, CharSequence text, int duration){
if (toast==null)
//创建一个Toast提示消息
toast = Toast.makeText(context, text, duration);
else
toast.setText(text);
//设置Toast提示消息在屏幕上的位置
toast.setGravity(Gravity.CENTER, 0, 0);
//显示消息
toast.show();
}
public static void closeToast(){
if(toast!=null){
toast.cancel();
}
}
现在:3.public class ToastUtil {
private static Toast toast = null;
public static void toastText(Context context, CharSequence text, int duration){
if (toast==null)
//创建一个Toast提示消息
toast = Toast.makeText(context, text, duration);
else
toast.setText(text);
//设置Toast提示消息在屏幕上的位置
toast.setGravity(Gravity.CENTER, 0, 0);
//显示消息
toast.show();
}
public static void closeToast(){
if(toast!=null){
toast.cancel();
toast = null;
}
}
Static Views
1.以前:
现在:public class MyInfo_MoreSetting_V35 extends BaseActivity implements OnClickListener, CheckUpdateBack {
private static TextView cacheSize;
private Bundle b;
private static CustomProgressDialog dlg;
private static TextView tv_update;
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
cacheSize = null;
tv_update = null;
longDlg.cancel();
longDlg = null;
}
2.以前:public class BillActivity extends ActivityWithTitleAndList_SpecFooter {
private ImageView img_null;
现在:public class BillActivity extends ActivityWithTitleAndList_SpecFooter {
private static ImageView img_null;
protected void onDestroy() {
super.onDestroy();
img_null= null;
}
Inner Classes
1.以前:public class ChooseCity extends BaseActivity implements Comparator {
final myThread mythread = new myThread();
class myThread extends Thread implements Runnable {
public Handler handler;
@Override
public void run() {
super.run();
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (0 == msg.arg2) {
JSONObject pjo = new JSONObject();
for (int i = 0; i < commonpro_lists.size(); i++) {
if (root_ob.containsKey(commonpro_lists.get(i))) {
pjo.put(commonpro_lists.get(i), root_ob.get(commonpro_lists.get(i)));
}
}
现在:public class ChooseCity extends BaseActivity implements Comparator {
final myThread mythread = new myThread(this,commonpro_lists, root_ob,
commoncity_lists,area_ob,area_lists,city_id,MyHandler);
mythread.start();
static class myThread extends Thread implements Runnable {
public Handler handler;
private WeakReference
private List
private JSONObject root_ob;
private List
private JSONObject area_ob;
private List
private String city_id;
private Handler MyHandler;
public myThread(ChooseCity chooseCity, List
List
String city_id, Handler MyHandler) {
chooseCityWeakReference = new WeakReference
this.commonpro_lists = commonpro_lists;
this.root_ob = root_ob;
this.commoncity_lists = commoncity_lists;
this.area_ob = area_ob;
this.area_lists = area_lists;
this.city_id = city_id;
this.MyHandler = MyHandler;
}
@Override
public void run() {
super.run();
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (0 == msg.arg2) {
JSONObject pjo = new JSONObject();
for (int i = 0; i < commonpro_lists.size(); i++) {
Anonymous Classes
1.以前:mWebView.setWebChromeClient(new MyWebChromeClient(this,progressbar));
现在:mWebView.setWebChromeClient(new MyWebChromeClient(this));
Handler:要用activity就要用弱引用指向的activity,并且在ondestroy里面要调用remove
1.以前:public class BaseActivity extends Activity implements CheckUpdateBack {
protected static Handler myHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
现在:protected Handler myHandler = new MyHandler(BaseActivity.this);
static class MyHandler extends Handler{
private WeakReference
public MyHandler(Context context){
weakRefCtx = new WeakReference
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (weakRefCtx.get()==null)
return;
protected void onDestroy() {
super.onDestroy();
payHandle.removeCallbacksAndMessages(null);
}
Threads
1.以前:new Thread(new Runnable() {
@Override
public void run() {
现在:MyRunnable runnable = new MyRunnable(mIRemoteService,payHandle,businessResult);
new Thread(runnable).start();
}
static class MyRunnable implements Runnable {
private IRemoteClientService mIRemoteService;
private Handler payHandle;
private String businessResult;
public MyRunnable(IRemoteClientService mIRemoteService,Handler payHandle,String businessResult){
this.mIRemoteService = mIRemoteService;
this.payHandle = payHandle;
this.businessResult = businessResult;
}
@Override
public void run() {
try {
String result = mIRemoteService.pay(businessResult);
Message message = new Message();
message.what = 10;
message.obj = result;
payHandle.sendMessage(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
TimerTask
Sensor Manager(还有各种通过getSystemService的到的,重现就是打开2个含有此服务的activity,详解http://www.tuicool.com/articles/yuiMbyy,https://github.com/pwittchen/NetworkEvents/issues/106)
1.以前:LoginActivity :protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginUserList = LoginUserDBManager.getInstance().getAllUser();
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
1.现在:protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginUserList = LoginUserDBManager.getInstance().getAllUser();
mWifiManager = (WifiManager) this.getApplicationContext().getSystemService(WIFI_SERVICE);
单例的使用:
1.以前:UserInfo userInfo = AppContext.getAppContext().getmCurrentUserInfo();
现在:UserInfo userInfo = ((AppContext)this.getApplication()).getmCurrentUserInfo();
2.以前:public static CrashHandler getInstance(Context context,
UncaughtExceptionHandler defaultHandler) {
if (mHandler == null)
mHandler = new CrashHandler(context, defaultHandler);
mContext = context ;
return mHandler;
}
现在:public static CrashHandler getInstance(Context context,
UncaughtExceptionHandler defaultHandler) {
WeakReference
if (mHandler == null){
if (weakRefCtx.get()!=null)
mHandler = new CrashHandler(context, defaultHandler);
}
if (weakRefCtx.get()!=null)
mContext = context ;
return mHandler;
}
Context:
1.以前:public class BocPay {
public boolean aboutMapQuery(Context context) {
this.context = context;
dialogMessage = "您尚未安装中国银行移动支付插件,立即安装并完成支付!";
mFlag = inexistenceOrNeedUpdate(packageName);
if (!mFlag) {// 未安装或需要更新
new AlertDialog.Builder(context).setTitle("提示信息 ").setMessage(dialogMessage)
现在:public class BocPay {
public boolean aboutMapQuery(Context context) {
weakRefCtx = new WeakReference
// this.context = weakRefCtx.get();
dialogMessage = "您尚未安装中国银行移动支付插件,立即安装并完成支付! ";
if (weakRefCtx.get()==null){
Log.d("leak","context软引用被回收了");
return true;
}
mFlag = inexistenceOrNeedUpdate(packageName);
if (!mFlag) {// 未安装或需要更新
new AlertDialog.Builder(weakRefCtx.get()).setTitle("提示信息 ").setMessage(dialogMessage)
.setPositiveButton("确定", new DialogInterface.OnClickListener()
2.以前:public class StringUtils {
public static SpannableString setPhoneSpan(final Context context, String text, final int color){
......
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Utils.call(context, phone);
}
......
现在:public class StringUtils {
public static SpannableString setPhoneSpan(final Context context, String text, final int color){
final WeakReference
......
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
if (weakRef.get()==null)
return;
Utils.call(weakRef.get(), phone);
}
......
第3方SDK要关闭:
1. public class ShareDownLoad extends Activity implements OnClickListener, PlatformActionListener {
@Override
protected void onDestroy() {
super.onDestroy();
ShareSDK.stopSDK(this);
}
context跟ApplicationContext选择(能用ApplicationContext就不用context):
1.以前:ShareSDK.initSDK(this);
现在:ShareSDK.initSDK(this.getApplicationContext());
2.以前:mgr = new AccountMgr(this);
现在:mgr = new AccountMgr(this.getApplicationContext());
3.以前:public class Navi_Amap_new extends ActivityWithTitleBar implements OnDistrictSearchListener,OnClickListener,OnMapLoadedListener,OnLocationGetListener {
ttsManager =new TTSController(this);
3.现在:ttsManager =new TTSController(this.getApplicationContext());
动态广播注册了要注销
1.以前:BaseActivity:
protected void onResume() {
super.onResume();
registerMainReceiver();
sendBroadcaseUpdateCount();
}
1.现在:protected void onDestroy() {
super.onDestroy();
unregisterMainReceiver();
}