`

androird 内存泄露 浅析

 
阅读更多

主要是记录工作中出现的问题和网上的一些解决方案

   一 java 内存分配

堆内存和栈内存

 

  • 1 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。
  • 2 堆是先进先出,而栈是先进后出
  • 3 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享的。
  • 4 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

   根据以上分析,我们得知:

       一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配;堆内存用于存放由new创建的对象和数组。即堆主要用来存放对象的,栈主要是用来执行程序的。如图:



 

 

 

 在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

 

 

  二 Java中垃圾产生的原因

  • 引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因。

   三 Java 垃圾回收机制

 

         那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),则认为这个(这些)对象不再被引用,可以被GC回收。



 

 

 

Vector v= new Vector(10);
 for (int i=1;i<100; i++){
        Object o=new Object();
         v.add(o);
         o=null;
       }

 

     垃圾回收的例子:

 

        在这个例子中,代码栈中存在Vector对象的引用v和Object对象的引用o。在For循环中,我们不断的生成新的对象,然后将其添加到Vector对象中,之后将o引用置空。问题是当o引用被置空后,如果发生GC,我们创建的Object对象是否能够被GC回收呢?

         答案是否定的。因为,GC在跟踪代码栈中的引用时,会发现v引用,而继续往下跟踪,就会发现v引用指向的内存空间中又存在指向Object对象的引用。也就是说尽管o引用已经被置空,但是Object对象仍然存在其他的引用,是可以被访问到的,所以GC无法将其释放掉。如果在此循环之后,Object对象对程序已经没有任何作用,那么我们就认为此Java程序发生了内存泄漏。

 

 四 android 内存泄露

      Android的内存管理与Java的内存管理相似。Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,也就是说每个应用程序都是在属于自己的进程中运行的。Android为不同类型的进程分配了不同的内存使用上限。

     Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的

 

    为什么会出现内存不够用的情况呢?原因主要有两个:


  • 由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
  • 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制

  五 内存泄露的几种类型

 

 

(一) Static变量的使用

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享(如private static ProgressDialog mDelMessProDialog;)。其生命周期取决于类的生命周期,即类的所有实例结束了,如果类还存在,其静态变量依然还存在,只有当类被卸载。静态变量才被销毁。

 

 

如:  private static Acitvity mContext;//

 那么如何有效避免这种引用的发生呢?

第一 尽量避免static成员变量引用资源耗费过多的实例,如Context.

 

第二 Context尽量使用Application Context,因为Application的Context生命周期较长,引用它不会出现内存泄漏问题。

 第三 使用WeakReference代替强引用,比如可以使用WeakReference<Context> mContextRef;

 

 

 

 线程(二)

 

•线程也是造成内存泄漏的一个重要的源头,线程产生内存泄漏的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

 

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函数是一个很费时的操作,当调用finish的时候Activity会销毁掉吗?
 事实上由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,同时它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
解决方案
   第一、将线程的内部类,改为静态内部类。
   第二、如果需要引用Acitivity,使用弱引用。
   另外在使用handler 的时候, 尤其用到循环调用的时候,在Activity 退出的时候注意移除。否则也会导致泄露
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);        
  :            //每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();
}
 
  事实上由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,
同时它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
(三) Bitmap 
可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了
解决方案:
   第一、及时的销毁。
   虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。
第二、设置一定的采样率。
   有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
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在内存快不足时得到有效的释放。
(四) 资源对象没关闭造成的内存泄露
  1.  资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
  2.  程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在长时间大量操作的情况下才会复现内存问题
  3.  这样就会给以后的测试和问题排查带来困难和风险。
(五)构造Adapter时,没有使用缓存的 convertView
Java代码:

 

public View getView(int position, View convertView, ViewGroup parent) {
View view = new View();
//通过inflate等找到布局 然后findViewById等 设置各个显示的

itemreturn view;

} 
 

 

 ListView滑动的过程中 很容易就会发现每次getView被执行 都会new出一个View对象 长此以往会产生很大的消耗 。
下面是使用convertView的情况:
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
//复用了回收的view 只需要直接作内容填充的修改就好了
} else {
view = new Xxx(...);
//没有供复用的view 按一般的做法新建view
}
return view;
}
 

       即:当ListView滑动的过程中 会有item被滑出屏幕 而不再被使用 这时候Android会回收这个条目的view 这个view也就是这里的convertView在上面的做法中 当item被移除屏幕的时候 我们会重新new一个View给新显示的item_new 而如果使用了这个convertView 我们其实可以复用它 ,这样就省去了new View的大量开销

     但是上面的仍然有缺陷 当我们的ListView中填充的item有多种形式时 比如微博中 有的item中包含图片 有的item包含视频 那么必然的 我们需要用到2种item的布局方式此时如果只是单纯判断convert是否存在 会造成回收的view不符合你当前需要的布局 而类似转换失败出错退出
 

参考文档:

 

http://fluagen.blog.51cto.com/146595/78018

http://www.cnblogs.com/xiaoran1129/archive/2012/11/29/2794860.html 
android内存泄露

http://my.oschina.net/u/165872/blog/108944

Android利用convertView优化ListView性能 

  • 大小: 12.6 KB
  • 大小: 24.4 KB
分享到:
评论

相关推荐

    Android防止内存溢出浅析.zip

    1. **Android内存管理机制** - **Dalvik/ART虚拟机**:Android系统使用Dalvik或ART虚拟机执行应用程序,它们都有自己的内存管理策略。 - **堆内存**:Java对象主要存储在堆中,Android为每个应用分配了一定量的堆...

    Android防止内存溢出浅析

    首先,我们需要理解Android内存管理的基本原理。Android应用程序基于Java语言,因此其内存机制与Java相似。Java的垃圾回收(GC)机制会自动回收不再使用的对象,但并不意味着开发者可以完全忽视内存管理。Android系统...

    基于Android防止内存溢出浅析

    本文将深入浅析如何在Android环境中预防和处理内存溢出问题。 首先,我们需要了解Android的内存模型。Android系统采用Dalvik或ART虚拟机来运行应用程序,每个应用都有自己的内存空间,当这个空间被耗尽时,就会触发...

    Android应用源码之防止内存溢出浅析.zip

    一、Android内存管理基础 Android系统采用Dalvik虚拟机(在Android 5.0之后改为ART)执行应用代码,其内存管理基于垃圾回收(Garbage Collection, GC)。GC会自动回收不再使用的对象,但开发者仍需关注内存占用,...

    安卓Android源码——防止内存溢出浅析.zip

    Android内存分为堆内存和栈内存,Java对象主要存储在堆中,而局部变量和方法信息存储在栈中。内存溢出通常发生在堆内存,当分配给应用的堆内存不足以创建新的对象时,就会发生溢出。 1. **对象生命周期与引用管理**...

    Android应用源码之防止内存溢出浅析-IT计算机-毕业设计.zip

    一、Android内存管理基础 Android系统采用Dalvik虚拟机(Dalvik Virtual Machine,DVM)进行应用执行,每个应用都有自己的虚拟机实例,内存空间独立。DVM使用垃圾回收机制(Garbage Collection,GC)自动回收不再...

    应用源码之防止内存溢出浅析.zip

    2. **Android内存结构**:Android系统有Dalvik堆、Native堆和图形内存等几个部分,了解这些内存区域的分配和回收规则对优化至关重要。 3. **内存泄漏**:未被正确释放的资源可能导致内存泄漏,例如匿名内部类、静态...

    防止内存溢出浅析

    6. **使用内存分析工具**:Android Studio提供了强大的内存分析工具,如Memory Profiler,可以帮助开发者检测内存泄漏、分析内存分配和GC行为。通过这些工具,可以定位并修复内存问题。 7. **优化代码结构**:良好...

    安卓开发-防止内存溢出浅析.zip

    使用Android Studio的Profiler工具进行内存分析,实时查看内存分配和GC情况,找出可能的内存泄漏点。 六、利用软引用和弱引用 软引用和弱引用可以帮助管理内存,它们在内存不足时会被GC回收。软引用常用于缓存,弱...

    Android_WP_SP浅析

    在Android的Native层开发中,为了管理和防止内存泄漏,Google引入了一套基于C++的智能指针系统,主要包含两个关键类型:sp(Strong Pointer)和wp(Weak Pointer)。它们都是基于类RefBase的引用计数机制来实现的,...

    Android JNI 浅析

    使用JNI时要注意内存管理,避免内存泄漏,以及处理好线程同步问题,因为Java层和Native层的线程模型并不相同。 总的来说,Android JNI为开发者提供了强大的功能,让Java和C/C++的结合成为可能,但同时也带来了复杂...

    Android编程内存溢出与防范方法浅析

    5. **使用工具检测**:使用Android Studio自带的内存分析工具或者第三方库如LeakCanary,可以帮助检测和定位内存泄漏。 了解并掌握这些防范措施,可以有效地减少Android应用中内存溢出的发生,从而提高应用性能和...

    深入浅析Android消息机制

    为避免内存泄漏,应谨慎处理非静态内部类的`Handler`实例,因为它们隐式持有外部类的引用。 其次,`Looper`是消息循环的驱动器,它不断地从`MessageQueue`中取出待处理的消息并交给关联的`Handler`。在Android系统...

    浅析Android Handler的使用误区与避免.pdf

    - **内存泄漏问题**:如果在Activity或Fragment中创建`Handler`,且没有正确处理Activity的生命周期,可能会导致内存泄漏。因为`Handler`持有对Activity的引用,即使Activity已经销毁,`Handler`仍然活跃,阻止...

    浅析安卓(Android)的性能优化

    1. 对象生命周期管理:确保正确地管理Activity、Fragment和其他对象的生命周期,避免因长时间持有强引用导致内存泄露。使用WeakReference或SoftReference可以防止内存泄露。 2. 避免静态变量的滥用:静态变量会一直...

Global site tag (gtag.js) - Google Analytics