内存处理机制

因为Android内存比较少,16M,有的是24M,虽然有着GC帮我们管理,但是我们还是应该对内存有简单的了解。

内存简介

看图



我们了解一下我们代码申请的数据都放在哪里。

  • 对象实例数据
    实际上是保存对象实例的属性,属性的类型和对象本身的类型标记等,但是不保存实例的方法。实例的方法是属于数据指令,是保存在Stack里面,也就是上面表格里面的类方法。
    对象实例在Heap中分配好以后,会在Stack中保存一个4字节的Heap内存地址,用来查找对象的实例。因为在Stack里面会用到Heap的实例,特别是调用实例的时候需要传入一个this指针。
  • 方法内部变量
    类方法的内部变量分为两种情况:简单类型保存在Stack中;对象类型在Stack中保存地址,在Heap 中保存值。
  • 非静态方法和静态方法
    非静态方法有一个隐含的传入参数,这个参数是dalvik虚拟机传进去的,这个隐含参数就是对象实例在Stack中的地址指针。因此非静态方法(在Stack中的指令代码)总是可以找到自己的专用数据(在Heap 中的对象属性值)。当然非静态方法也必须获得该隐含参数,因此非静态方法在调用前,必须先new一个对象实例,获得Stack中的地址指针,否则dalvik虚拟机将无法将隐含参数传给非静态方法。
    静态方法没有隐含参数,因此也不需要new对象,只要class文件被ClassLoader load进入JVM的Stack,该静态方法即可被调用。所以我们可以直接使用类名调用类的方法。当然此时静态方法是存取不到Heap 中的对象属性的。
  • 静态属性和动态属性
    静态属性是保存在Stack中的,而不同于动态属性保存在Heap 中。正因为都是在Stack中,而Stack中指令和数据都是定长的,因此很容易算出偏移量,所以类方法(静态和非静态)都可以访问到类的静态属性。也正因为静态属性被保存在Stack中,所以具有了全局属性。

内存区别

  • 栈内存
    栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄
  • 堆内存
    堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

内存管理

上面简单介绍了一下Android内存基本知识,下面我们简单介绍一下内存管理机制。
GC帮我们管理者内存,只有被废弃的对象才会被回收。那哪些才是被废弃的?简单的说,没有被引用的对象就是被废弃的对象。Android和Java内存管理相似,采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

管理内存是为了防止内存溢出,毕竟Android内存比较少。
发生内存溢出的原因:

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

解决办法:

  • 谨慎使用static
    static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
    public class ClassName {
    private static Context mContext;
    //省略
    }
    以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。
    我们举Android文档中的一个例子。
    private static Drawable sBackground;
    @Override
    protected void onCreate(Bundle state) {
    super.onCreate(state);
    TextView label = new TextView(this);
    label.setText("Leaks are bad");
    if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
    }
    label.setBackgroundDrawable(sBackground);
    setContentView(label);
    }
    sBackground, 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
    Drawable->TextView->Context
    所以,最终该Context也没有得到释放,发生了内存泄露。
    如何才能有效的避免这种引用的发生呢?
    1.应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
    2.Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
    ** 3.使用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函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
    由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。


    有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
    这种线程导致的内存泄露问题应该如何解决呢?
    第一、将线程的内部类,改为静态内部类。
    第二、在线程内部采用弱引用保存Context引用。
    解决的模型如下:
    public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends
    AsyncTask<Params, Progress, Result> {
    protected WeakReference<WeakTarget> mTarget;
    public WeakAsyncTask(WeakTarget target) {
    mTarget = new WeakReference<WeakTarget>(target);
    }
    /** {@inheritDoc} /
    @Override
    protected final void onPreExecute() {
    final WeakTarget target = mTarget.get();
    if (target != null) {
    this.onPreExecute(target);
    }
    }
    /
    * {@inheritDoc} /
    @Override
    protected final Result doInBackground='#'" /span>
    final WeakTarget target = mTarget.get();
    if (target != null) {
    return this.doInBackground='#'" /span>
    } else {
    return null;
    }
    }
    /
    * {@inheritDoc} */
    @Override
    protected final void onPostExecute(Result result) {
    final WeakTarget target = mTarget.get();
    if (target != null) {
    this.onPostExecute(target, result);
    }
    }
    protected void onPreExecute(WeakTarget target) {
    // No default action
    }
    protected abstract Result doInBackground(WeakTarget target, Params... params);
    protected void onPostExecute(WeakTarget target, Result result) {
    // No default action
    }
    }
    事实上,线程的问题并不仅仅在于内存泄露,还会带来一些灾难性的问题。由于本文讨论的是内存问题,所以在此不做讨论。

  • Bitmap
    可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。
    如何解决Bitmap带给我们的内存问题?
    1.及时的销毁。
    虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
    2.设置一定的采样率。
    有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
    private ImageView preview;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
    Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
    preview.setImageBitmap(bitmap);

    3.巧妙的运用软引用(SoftRefrence)
    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下例:
    /*本例子随便写的,来说明用法,并未验证/
    private class MyAdapter extends BaseAdapter {
    private ArrayList<SoftReference<Bitmap>> mBitmapRefs = new ArrayList<SoftReference<Bitmap>>();
    private ArrayList<Value> mValues;
    private Context mContext;
    private LayoutInflater mInflater;
    MyAdapter(Context context, ArrayList<Value> values) {
    mContext = context;
    mValues = values;
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    public int getCount() {
    return mValues.size();
    }
    public Object getItem(int i) {
    return mValues.get(i);
    }
    public long getItemId(int i) {
    return i;
    }
    public View getView(int i, View view, ViewGroup viewGroup) {
    View newView = null;
    if(view != null) {
    newView = view;
    } else {
    newView =(View)mInflater.inflate(R.layout.image_view, false);
    }
    Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);
    mBitmapRefs.add(new SoftReference<Bitmap>(bitmap)); //此处加入ArrayList
    ((ImageView)newView).setImageBitmap(bitmap);
    return newView;
    }
    }

说明

以上只是描述一些常用的场景,其实,要减小内存的使用,其实还有很多方法和要求。比如不要使用整张整张的图,尽量使用9path图片。Adapter要使用convertView等等,好多细节都可以节省内存。这些都需要我们去挖掘。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容