美文网首页安卓Android面试题Android 开发经验集Android
Android面试一天一题(Day 29:内存泥潭(下))

Android面试一天一题(Day 29:内存泥潭(下))

作者: goeasyway | 来源:发表于2016-10-23 13:36 被阅读4221次

    上一节有介绍了一些和内存相关的基础知识,这一节就讲一下怎么发现和处理内存问题。对于我们来说,最容易发现的内存问题当然是OOM(OutOfMemoryError),应用直接Crash,日志也会很清晰的标明哪个对象OOM了。这个解决起来也不难,常见的Bitmap OOM相信大家也知道怎么处理。

    相对于OOM较麻烦一点的就是内存泄露(Memory Leak),每次就露那么一点,就像温水煮青蛙一样,很难发现有变化。内存泄漏也是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,之后我们再申请新的内存时会及其容易产生OOM。

    内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到GC roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。


    注意:GC过于频繁容易出现内存抖动,这也是造成应用卡顿的常见原因。

    也可以通过命行的方式查看:

    adb shell dumpsys meminfo <package_name|pid> [-d]
    

    具体的数值意义可以查看官网的说明:https://developer.android.com/studio/profile/investigate-ram.html

    MAT内存分析工具

    详细的内存使用情况,可以通过Android Studio的Android Monitor界面,在Memory那栏有上几个小图标,点击有一个向下箭头的图标会自动生成并打开的HPROF视图。

    开发中如何避免内存泄漏

    这点我比较喜欢问面试者,希望面试者能罗列出一些他自己遇到过的情况。通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多(它里面有N多资源的引用),影响比较明显。下面就示例两种错误的引用方式。

    错误的单例模式

    public class Singleton {
        private static Singleton instance;
        private Context mContext;
    
        private Singleton(Context context) {
            this.mContext = context;
        }
    
        public static Singleton getInstance(Context context) {
            if (instance == null) {
                instance = new Singleton(context);
            }
            return instance;
        }
    }
    

    这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

    View持有Activity引用

    public class MainActivity extends Activity {
        private static Drawable mDrawable;
    
        @Override
        protected void onCreate(Bundle saveInstanceState) {
            super.onCreate(saveInstanceState);
            setContentView(R.layout.activity_main);
            ImageView iv = new ImageView(this);
            mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
            iv.setImageDrawable(mDrawable);
        }
    }
    

    有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

    其实避免Activity的泄漏的方式可以总结为:不要让生命周期长于Activity的对象持有到Activity的引用。

    在开发中,我们也可以给一些初级的工程师相关的建议,如:

    1. 注意单例模式和静态变量是否会持有对Context的引用;
    1. 注意监听器的注销;(在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。)
    2. 不要在Thread或AsyncTask中的引用Activity;

    小结

    内存泄漏检测并不属于一个经常会做的事情,所以上面写的一些东西难免会有一些错误。不过我认为在面试中,更关注的是面试者做何去发现和解决这个问题,然后是否会对遇到过的问题有一个总结,至于细节上的东西在真正做的时候会直接得到工具或LOG的反馈,不一定非常记得很清楚的人才说明他会这个东西。

    相关文章

      网友评论

      • IDO0:内存泄漏原因就是变量的引用大于了变量的作用域。
        声明一个变量时首先要考虑到这个对象的初始值是什么以及什么时候使用结束被销毁。
      • _番茄沙司:handler和asynctask都可以使用弱引用来避免内存泄漏
      • 机智的鲁智深:引用图是有向图吧,ImageView引用静态变量,但静态变量不见得会引用ImageView啊。
        1bce3892de09: @机智的鲁智深 会的,看view的setbackground方法的代码就知道了
      • 36919d147bc3:这些是内存泄露基本内容,希望楼主发些更深入的文章
        goeasyway:@QiuGong2016 收到
      • wipen:谢谢分享。

        对于“View持有Activity引用”这个例子我抱有疑问,因此特地写代码实验了一下。
        使用LeakCanary测试,当Activity握有static类型的drawable时并不会引起内存泄露。
        虽然ImageView的setImageDrawable(@Nullable Drawable drawable)方法中有一段调用了d.setCallback(this);但是这里使用的是弱引用(API 23)
        因此Drawable静态实例并没有间接引用Activity实例,也不会引起内存泄露。

        当然,如果将ImageView实例写作static是会引起内存泄露的。
        我想你是希望举一个不那么明显的例子,指出间接引用这种情况。
        1bce3892de09: @wipen 老版本的Android源码是强引用,不知道谷歌什么时候解决的这个问题。不过不能因为新版本解决了这个问题就当这个问题不存在,毕竟你也不能让自己的应用在部分机型产生溢出
      • rrrrht:博主,不太明白“view持有activity的引用”那个例子, Imageview是个局部变量,及时添加到某容器,也是Imageview持有静态drawble的引用,而不是静态drawable持有Imageview, 所以,我觉得这段代码,不会泄露。 不知道对不,请指教
        hackware:@feimao drawable是通过弱引用持有ImageView的
        goeasyway:@feimao :+1:
        1bce3892de09: @任昊霆 drawable是静态的,不会被释放,把这个drawable设置给view当背景,drawable就会持有这个view,而view又会持有context,也就是Activity,所以导致刚才提到的这些对象都无法释放
      • 1d65221a9608:内存泄漏,怎么样才可以很好的解决,楼主可以多介绍一点比较易懂的资料让小白去学习学习嘛,除了上述说了尽量不要让比Activity声明周期长的的对象持有Activity的引用外,还有其他的嘛
        RyanYans32:不要让比Activity(本对象)声明周期长的的对象持有Activity(本对象)的强引用,几乎涵盖所有内存泄露(非静态内部类导致内存泄露、错误使用SingleTon、View持有Activity等等的导引几乎都是这个问题产生)
      • c5a7829560b5:那正确怎么写的呢
      • 862c9fb99650:谢谢分享,一直都有点模糊,谢谢
      • cd7875ee1e5f:真的很感谢你的文章,特别是面试系列,对我的帮助很大!由衷的感谢作者的奉献精神
      • 孙科技:谢谢,对我很有帮助,内存泄露一直是个弱项,有更详细的资料吗?

      本文标题:Android面试一天一题(Day 29:内存泥潭(下))

      本文链接:https://www.haomeiwen.com/subject/mwtpyttx.html