`

Android的内存泄漏和调试

阅读更多
Android的内存泄漏和调试

一、 Android的内存机制
Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的.
那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

二、Android的内存溢出
Android的内存溢出是如何发生的?
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
为什么会出现内存不够用的情况呢?我想原因主要有两个:
由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。

三、常见的内存泄漏
1.万恶的static
  static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

public class ClassName {     
    private static Context mContext;       //省略

以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.

如何才能有效的避免这种引用的发生呢?
    第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
    第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
    第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;

2.线程惹的祸
线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
public class MyActivity extends Activity {    
@Override    
public void onCreate(Bundle savedInstanceState) {        
  super.onCreate(savedInstanceState);        
  setContentView(R.layout.main);        
  new MyThread().start();    
}      
private class MyThread extends Thread{        
@Override        
  public void run() {            
  super.run();            
  //do somthing        
}    
}

    这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
    由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

这种线程导致的内存泄露问题应该如何解决呢?
    第一、将线程的内部类,改为静态内部类。
    第二、在线程内部采用弱引用保存Context引用。
   
    另外,我们都知道Hanlder是线程与Activity通信的桥梁,我们在开发好多应用中会用到线程,有些人处理不当,会导致当程序结束时,线程并没有被销毁,而是一直在后台运行着,当我们重新启动应用时,又会重新启动一个线程,周而复始,你启动应用次数越多,开启的线程数就越多,你的机器就会变得越慢。
package com.tutor.thread; 
import android.app.Activity; 
import android.os.Bundle; 
import android.os.Handler; 
import android.util.Log; 
public class ThreadDemo extends Activity { 
    private static final String TAG = "ThreadDemo"; 
    private int count = 0; 
    private Handler mHandler =  new Handler(); 
     
    private Runnable mRunnable = new Runnable() { 
         
        public void run() { 
            //为了方便 查看,我们用Log打印出来  
            Log.e(TAG, Thread.currentThread().getName() + " " +count); 
            count++; 
            setTitle("" +count); 
            //每2秒执行一次  
            mHandler.postDelayed(mRunnable, 2000); 
        } 
         
    }; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main);  
        //通过Handler启动线程  
        mHandler.post(mRunnable); 
    } 
     
}
所以我们在应用退出时,要将线程销毁,我们只要在Activity中的,onDestory()方法处理一下就OK了,如下代码所示:
@Override 
  protected void onDestroy() { 
    mHandler.removeCallbacks(mRunnable); 
    super.onDestroy(); 
  }

3.超级大胖子Bitmap
可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。

    如何解决Bitmap带给我们的内存问题?

    第一、及时的销毁。
    虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

    第二、设置一定的采样率。
    有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
private ImageView preview; 
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一 
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);  preview.setImageBitmap(bitmap);

第三、巧妙的运用软引用(SoftRefrence)
    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。
  

4.行踪诡异的Cursor
    Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
    然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉
   
5.构造Adapter时,没有使用缓存的 convertView
描述:
  以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法:
public View getView(int position, View convertView, ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
  由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:
android.widget.AbsListView.java --> void addScrapView(View scrap) 方法。
示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = new Xxx(...);
  ... ...
  return view;
}
修正示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView != null) {
  view = convertView;
  populate(view, getItem(position));
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}

小结:
static:引用了大对象如context
线程:切屏时Activity因为线程引用而没有如期被销毁;handler有关,Activity意外终止但线程还在
Bitmap:要及时recycle,降低采样率
Cursor:要及时关闭
Adapter:没有使用缓存的convertView

四、内存泄漏调试:
(1).内存监测工具 DDMS --> Heap
无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方。Android tools中的DDMS就带有一个很不错的内存监测工具Heap(这里我使用eclipse的ADT插件,并以真机为例,在模拟器中的情况类似)。用 Heap监测应用进程使用内存情况的步骤如下:
1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;
2. 将手机通过USB链接至电脑,链接时需要确认手机是处于“USB调试”模式,而不是作为“Mass Storage”;
3. 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息;
4. 点击选中想要监测的进程,比如system_process进程;
5. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标;
6. 点击Heap视图中的“Cause GC”按钮;
7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。
说明:
a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作;
b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。
  如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:
a) 不断的操作当前应用,同时注意观察data object的Total Size值;
b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
c) 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,
  直到到达一个上限后导致进程被kill掉。
d) 此处已system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过3.55后进程就会被kill。
 
  总之,使用DDMS的Heap视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。

分享到:
评论

相关推荐

    Android 内存泄漏调试经验分享

    ### Android内存泄漏调试经验分享 #### 一、概述 在Android开发中,内存泄漏是一个常见且需要重点关注的问题。由于Android设备通常配置有限,尤其是内存资源较为紧张,因此开发者需要格外注意避免内存泄漏的发生,...

    使用Android Studio检测内存泄露(LeakCanary)

    Android Studio提供了多种工具帮助开发者检测和解决内存泄露,其中包括Memory Profiler和LeakCanary。 Memory Profiler是Android Studio内置的一个强大工具,它允许开发者实时监控应用的内存使用情况。在Memory面板...

    Node.js-Androidnative层代码内存泄漏问题调试利器

    总的来说,掌握有效的内存泄漏调试方法对于提高Android应用的性能和稳定性至关重要。通过利用Node.js和LeakTracer这样的工具,开发者可以在Native层代码的内存管理上达到更高的精度,避免因内存泄漏导致的问题,从而...

    Android应用程序内存泄漏的分析.pdf

    以上知识点涉及了Android内存泄漏分析的多个方面,包括理论知识、工具使用、操作实践和问题诊断技巧。理解和掌握这些知识点对Android开发人员来说是非常重要的,它可以帮助他们在日常开发中有效地避免和解决内存泄漏...

    Android_内存泄漏研究及调试.doc )

    ### Android 内存泄漏研究及调试 #### 一、概述 在Android开发中,内存管理是确保应用稳定性和性能的关键因素之一。与Java程序类似,Android应用也面临着内存泄漏的风险,尤其是在资源有限的移动设备上,内存泄漏...

    Android内存泄漏信息介绍(英文)

    总结起来,Android内存管理的核心知识点包括:并发垃圾回收(GC_CONCURRENT)、堆大小限制和申请、大堆内存的使用与性能影响,以及内存泄漏的检测和分析方法。开发者在编写应用时,应当有意识地管理内存使用,避免...

    Android内存泄露检测_mat

    以下将详细介绍如何使用MAT进行Android内存泄露的检测。 **安装MAT** MAT可以作为独立的应用程序运行,也可以作为Eclipse IDE的插件。下载MAT的相应版本(例如MemoryAnalyzer-1.1.1.20110824-linux.gtk.x86_64.zip...

    android 内存 调试

    本篇文章将深入探讨Android内存调试的基本思路与工具,帮助开发者更好地理解内存使用情况,优化应用程序,避免内存泄漏等问题。 ### Android内存管理概述 在Android系统中,内存资源的分配和管理由Linux内核负责。...

    android内存泄露

    在Android开发中,内存泄露是一个严重的问题,它会导致应用程序占用过多的内存,...通过阅读"Android内存泄漏调试教程.pdf"和"有关内存泄露"等相关资料,开发者可以更深入地学习这一主题,提升自己的Android开发技能。

    Python-支持WindowsLinuxMac和Android的内存调试程序

    内存调试是软件开发过程中至关重要的一环,它帮助开发者识别和修复程序中的内存泄漏、野指针、悬挂指针等内存管理问题。 内存泄漏是程序在分配内存后未能正确释放,导致系统资源逐渐耗尽。野指针是指未初始化或已...

    Android App调试内存泄露之Cursor.zip

    本话题将深入探讨如何针对特定的内存泄漏问题——Cursor泄漏进行调试。Cursor对象是Android数据库操作中的关键组件,用于检索数据集,如果使用不当,很容易引发内存泄漏。以下是关于这个主题的详细讲解。 1. **...

    Android 程序调试技术学习

    另外,内存泄漏是Android应用常见的问题,可以使用Memory Profiler工具检测内存分配和释放,找出可能导致内存泄漏的地方。 最后,Android Studio的Instant Run功能可以在不重新部署整个应用的情况下,快速更新和...

    webView解决内存泄漏

    9. **定期检查和调试**:使用Android Studio的内存分析工具(Memory Profiler)进行定期检查,可以帮助发现并定位内存泄漏。通过查看内存快照,找出长时间存活的对象,尤其是那些与WebView相关的对象。 10. **保持...

    Android调试工具及方法

    Android Studio提供了一系列内存分析工具,如Memory Profiler,用于检测内存泄漏和过度对象分配。通过实时监控应用的内存使用情况,开发者可以识别出可能导致应用崩溃或性能下降的问题。内存快照功能允许对比不同...

    M8 android dump内存工具

    在Android系统中,内存分析是调试和优化应用性能的关键步骤。M8 android dump内存工具是一个专为M8设备设计的工具,它可以帮助开发者获取设备内存的状态信息,以便于诊断内存泄漏、性能瓶颈等问题。本文将详细讲解这...

    内存泄露Demo leaky app

    内存泄露是计算机科学中一个非常重要的概念,尤其是在iOS和Android等移动应用开发中。当我们谈论“内存泄露Demo leaky app”时,我们指的是一个应用程序在使用内存后未能正确释放它,导致系统资源的浪费和可能的性能...

    android手机内存分配小结

    对于Android内存的监控和调试,我们可以使用adb工具进行深入分析。在没有专门的调试工具如trace32时,可以执行以下步骤: 1. 连接adb shell到设备。 2. 创建并挂载一个debugfs文件系统:`mkdir /data/debug`,然后`...

Global site tag (gtag.js) - Google Analytics