• 80.Android之内存管理


    转载:http://www.jianshu.com/p/9fb0a795da93

    1. Android中的内存

    1.1 Android中的垃圾回收机制

    Android 平台最吸引开发者的一个特性:有垃圾回收机制,无需手动管理内存,Android 系统会自动跟踪所有的对象,并释放那些不再被使用的对象

    • Young Generation 新生代

      1. 大多数新建对象都位于Eden(伊甸园)区
      2. 当Eden区被对象填满时,就会执行Minor GC(轻量GC)。并把所有存活下来的对象转移到其中一个survivor区
      3. Survivor Space:S0、S1有两个,存放每次垃圾回收后存活的对象
      4. Minor GC 同样会检查survivor区中存活下来的对象,并把他们转移到另一个survivor区,这样在一段时间内,总会有一个空的survivor区

    • Old Generation 老生代

      1. 存放长期存活的对象和经过多次Minor GC后依然存活下来的对象
      2. 满了进行Major GC(较重GC)

    • Permanent Generation 永久代

    • 存放方法区,方法区中有,要加载的类信息、静态变量、final类型的常量、属性和方法信息

    1.2 垃圾回收

    • 内存占用过多,需要为新对象分配空间
    • 不同的虚拟机发生GC时采用的策略不同,可能会暂停当前程序的执行

    1.3 垃圾回收机制&FPS

    • Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,那么整个过程如果保证在16ms以内就能达到一个流畅的画面。60FPS
    • 如果某一帧的操作超过了16ms就会让用户感觉到卡顿
    • UI渲染过程发生GC,导致某一帧绘制时间超过16ms

    1.4 内存泄漏

    在整个Android开发过程中,内存泄漏是导致OOM(Out Of Memory内存溢出)的一个重要因素

    1. 应用程序分配了大量不能回收的对象
    2. 这导致可分配的内存越来越少
    3. 当新对象的创建需要的内存不够
    4. 当发现内存不够就会调用一次GC进行垃圾回收
    5. 结果:就会发生卡顿

    1.5 内存抖动

    原因:内存抖动是因为应用程序在短时间内创建大量的对象,又被马上释放。

    1. 瞬间产生大量的对象会严重占用Young Generation的内存区域
    2. 当达到阈值,剩余空间不够,就会触发GC从而导致刚产生的对象又很快被回收。
    3. 即时每次分配的对象占用了很少的内存,频分GC叠加在一起会增加Heap的压力
    4. 从而触发更多其他类型的GC。
    5. 结果:这个操作有可能会影响到帧率,并使用户感知到性能问题

    2. 内存检测工具

    2.1 Memory Monitor 内存监视器

    • 优点
      • 方便显示内存使用和GC情况
      • 快速定位卡顿是否和GC有关
      • 快速定位Crash崩溃是否和内存占用过高有关
      • 快速定位潜在的内存泄漏问题
      • 简单易用
    • 缺点
      • 不能准确定位问题

    2.2 Allocation Tracker 分配跟踪器

    • 优点
      • 定位代码中分配对象的类型,大小,时间,线程,堆栈等信息
      • 定位内存抖动问题
      • 配合HeapViewer一起定位内存泄漏问题
    • 缺点
      • 使用复杂
    • 显示所有对象的信息(环形图)

    2.3 Heap Viewer 堆视图

    • 优点
      • 内存快照信息
      • 每次GC之后收集一次信息
      • 查找内存泄漏利器
    • 缺点
      • 使用复杂
    • 显示已分配的对象大小信息(包视图)

    2.4 Leak Canary

    https://github.com/square/leakcanary

    • 引用

        dependencies {
            debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
            releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
        }
    • 使用

        // 内存泄漏检测
        private RefWatcher refWatcher;
        public static RefWatcher getRefWatcher(Context context) {
            TTApplication application = (TTApplication) context.getApplicationContext();
            return application.refWatcher;
        }
        @Override
        public void onCreate() {
            super.onCreate();
            refWatcher = LeakCanary.install(this);        // 检测
        }
      
        public void onDestroy() {
            RefWatcher refWatcher = TTApplication.getRefWatcher(getActivity());
            refWatcher.watch(this);                        //内存泄露检测
        }</code>

    3. 常见的内存泄漏问题

    3.1 单例造成的泄漏

    将Context对象保存在单例模式中,instance对象本身持有一个Context对象的引用,活动即时被销毁也不能被回收,因为静态变量一直持有它的引用

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

    可以改为

    public class AppManager {
        private static AppManager instance;
        private Context context;
        private AppManager(Context context) {
    
            //    使用Application的Context(也可以用自定义的Application)
            this.context = context.getApplicationContext();        
        }
        public static AppManager getInstance(Context context) {
            if (instance != null) {
                instance = new AppManager(context);
            }
            return instance;
        }
    }

    3.2 非静态内部类的静态实例造成的泄漏

    静态的sResource在创建时会间接持有一个MainActivity实例的引用,导致MainActivity无法被回收

    public class MainActivity extends Activity {
        private static TestResource sResource = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            if (sResource == null) {
                sResource = new TestResource();
            }
            // ...
        }
    
        // 非静态内部类
        class TestResource {
            // ...
        }
    }

    将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例

    如果用到Context就使用Application的Context

    但是Dialog不能使用Application和Service的Context

    3.3 Handler 造成的内存泄漏问题

    当创建匿名对象时,该对象会间接持有外部类实例的一个引用,mHandler对象本身会持有MainActivity的引用,导致MainActivity销毁后无法即时被回收

    public class MainActivity extends Activity {
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            loadData();
        }
    
        private void loadData() {
            // ...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
    }

    在Activity中避免使用非静态内部类,比如将Handler声明为静态的,这样Handler的存活时间就与Activity无关了

    同时引入弱引用的方式引入Activity,避免将Activity作为Context传入

    使用前判空

    public class MainActivity extends Activity {
        private static class MyHandler extends Handler {
            private final WeakReference<MainActivity> mActivity;
    
            private MyHandler(MainActivity activity){
                mActivity = new WeakReference<MainActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            loadData();
        }
    
        private void loadData() {
            // ...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
    }

    3.4 集合类泄漏

    • 如果集合类是全局的变量(类中的静态属性,全局性的map等既有静态引用或final一直指向它)
    • 没有相应的删除机制
    • 很可能导致集合所占用的内存只增不减

    4. 避免内存泄漏的方法

    1. 尽量不要让静态变量引用Activity
    2. 使用WeakReference弱引用,会保证GC时会被回收
    3. 使用静态内部类来代替内部类,静态内部类不持有外部类的引用
    4. 静态内部类使用弱引用来引用外部类
    5. 在声明周期结束的时候释放资源

    5. 减少内存使用

    1. 使用更轻量的数据结构(SpareArray代替HashMap)

      • Google自己定义的类占用内存更小
    2. 避免在onDraw方法中创建对象

      • onDraw()方法被频繁调用,在其中创建对象会导致临时对象过多,发生内存抖动
    3. 对象池(Message.obtain())

      • 当一定要在onDraw中创建对象,推荐使用对象池
      • 相当于对象缓冲,在创建时查找是否已经存在对象,没有在创建
    4. LRUCache

      • 大大减少内存使用
    5. Bitmap内存复用,压缩(inSampleSize,inBitmap)

    6. StringBuilder

      • 代替String,尤其是进行拼接操作时
  • 相关阅读:
    Unreal Engine 4官网教程
    快速上手制作暗黑类游戏动作打击感的一些要点
    充分利用 UE4 中的噪声
    综合帖:多角度闲聊游戏打击感
    鹅厂内部分享:七步教你从无到有做数值
    二叉树、二叉搜索树、平衡二叉树、B树、B+树的精确定义和区别探究
    MySQL的InnoDB索引原理详解
    分布式核心
    Mysql 原理以及常见mysql 索引等
    redis核心原理
  • 原文地址:https://www.cnblogs.com/benchao/p/6032222.html
Copyright © 2020-2023  润新知