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性能 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值