Android 内存管理

JoeannL47 8年前
   <h2><strong>1. Android中的内存</strong></h2>    <h3><strong>1.1 Android中的垃圾回收机制</strong></h3>    <p>Android 平台最吸引开发者的一个特性:有垃圾回收机制,无需手动管理内存,Android 系统会自动跟踪所有的对象,并释放那些不再被使用的对象</p>    <ul>     <li> <p>Young Generation 新生代</p>      <ol>       <li>大多数新建对象都位于Eden(伊甸园)区</li>       <li>当Eden区被对象填满时,就会执行Minor GC(轻量GC)。并把所有存活下来的对象转移到其中一个survivor区</li>       <li>Survivor Space:S0、S1有两个,存放每次垃圾回收后存活的对象</li>       <li> <p>Minor GC 同样会检查survivor区中存活下来的对象,并把他们转移到另一个survivor区,这样在一段时间内,总会有一个空的survivor区</p> </li>      </ol> </li>     <li> <p>Old Generation 老生代</p>      <ol>       <li>存放长期存活的对象和经过多次Minor GC后依然存活下来的对象</li>       <li> <p>满了进行Major GC(较重GC)</p> </li>      </ol> </li>     <li> <p>Permanent Generation 永久代</p> </li>     <li> <p>存放方法区,方法区中有,要加载的类信息、静态变量、final类型的常量、属性和方法信息</p> </li>    </ul>    <h3><strong>1.2 垃圾回收</strong></h3>    <ul>     <li>内存占用过多,需要为新对象分配空间</li>     <li>不同的虚拟机发生GC时采用的策略不同,可能会暂停当前程序的执行</li>    </ul>    <h3><strong>1.3 垃圾回收机制&FPS</strong></h3>    <ul>     <li>Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,那么整个过程如果保证在16ms以内就能达到一个流畅的画面。60FPS</li>     <li>如果某一帧的操作超过了16ms就会让用户感觉到卡顿</li>     <li>UI渲染过程发生GC,导致某一帧绘制时间超过16ms</li>    </ul>    <h2>1.4 内存泄漏</h2>    <p>在整个Android开发过程中,内存泄漏是导致OOM(Out Of Memory内存溢出)的一个重要因素</p>    <ol>     <li>应用程序分配了大量不能回收的对象</li>     <li>这导致可分配的内存越来越少</li>     <li>当新对象的创建需要的内存不够</li>     <li>当发现内存不够就会调用一次GC进行垃圾回收</li>     <li>结果:就会发生卡顿</li>    </ol>    <h3><strong>1.5 内存抖动</strong></h3>    <p>原因:内存抖动是因为应用程序在短时间内创建大量的对象,又被马上释放。</p>    <ol>     <li>瞬间产生大量的对象会严重占用Young Generation的内存区域</li>     <li>当达到阈值,剩余空间不够,就会触发GC从而导致刚产生的对象又很快被回收。</li>     <li>即时每次分配的对象占用了很少的内存,频分GC叠加在一起会增加Heap的压力</li>     <li>从而触发更多其他类型的GC。</li>     <li>结果:这个操作有可能会影响到帧率,并使用户感知到性能问题</li>    </ol>    <h2><strong>2. 内存检测工具</strong></h2>    <h3><strong>2.1 Memory Monitor 内存监视器</strong></h3>    <ul>     <li>优点      <ul>       <li>方便显示内存使用和GC情况</li>       <li>快速定位卡顿是否和GC有关</li>       <li>快速定位Crash崩溃是否和内存占用过高有关</li>       <li>快速定位潜在的内存泄漏问题</li>       <li>简单易用</li>      </ul> </li>     <li>缺点      <ul>       <li>不能准确定位问题</li>      </ul> </li>    </ul>    <h3><strong>2.2 Allocation Tracker 分配跟踪器</strong></h3>    <ul>     <li>优点      <ul>       <li>定位代码中分配对象的类型,大小,时间,线程,堆栈等信息</li>       <li>定位内存抖动问题</li>       <li>配合HeapViewer一起定位内存泄漏问题</li>      </ul> </li>     <li>缺点      <ul>       <li>使用复杂</li>      </ul> </li>     <li>显示所有对象的信息(环形图)</li>    </ul>    <h3><strong>2.3 Heap Viewer 堆视图</strong></h3>    <ul>     <li>优点      <ul>       <li>内存快照信息</li>       <li>每次GC之后收集一次信息</li>       <li>查找内存泄漏利器</li>      </ul> </li>     <li>缺点      <ul>       <li>使用复杂</li>      </ul> </li>     <li>显示已分配的对象大小信息(包视图)</li>    </ul>    <h3><strong>2.4 Leak Canary</strong></h3>    <ul>     <li> <p>引用</p> <pre>  <code class="language-java">dependencies {        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'    }</code></pre> </li>     <li> <p>使用</p> <pre>  <code class="language-java">// 内存泄漏检测    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></code></pre> </li>    </ul>    <h2><strong>3. 常见的内存泄漏问题</strong></h2>    <h3><strong>3.1 单例造成的泄漏</strong></h3>    <p>将Context对象保存在单例模式中,instance对象本身持有一个Context对象的引用,活动即时被销毁也不能被回收,因为静态变量一直持有它的引用</p>    <pre>  <code class="language-java">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;      }  }</code></pre>    <p>可以改为</p>    <pre>  <code class="language-java">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;      }  }</code></pre>    <h3>3.2 非静态内部类的静态实例造成的泄漏</h3>    <p>静态的sResource在创建时会间接持有一个MainActivity实例的引用,导致MainActivity无法被回收</p>    <pre>  <code class="language-java">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 {          // ...      }  }</code></pre>    <p>将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例</p>    <p>如果用到Context就使用Application的Context</p>    <p>但是Dialog不能使用Application和Service的Context</p>    <h3><strong>3.3 Handler 造成的内存泄漏问题</strong></h3>    <p>当创建匿名对象时,该对象会间接持有外部类实例的一个引用,mHandler对象本身会持有MainActivity的引用,导致MainActivity销毁后无法即时被回收</p>    <pre>  <code class="language-java">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);      }  }</code></pre>    <p>在Activity中避免使用非静态内部类,比如将Handler声明为静态的,这样Handler的存活时间就与Activity无关了</p>    <p>同时引入弱引用的方式引入Activity,避免将Activity作为Context传入</p>    <p>使用前判空</p>    <pre>  <code class="language-java">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);      }  }</code></pre>    <h3><strong>3.4 集合类泄漏</strong></h3>    <ul>     <li>如果集合类是全局的变量(类中的静态属性,全局性的map等既有静态引用或final一直指向它)</li>     <li>没有相应的删除机制</li>     <li>很可能导致集合所占用的内存只增不减</li>    </ul>    <h2><strong>4. 避免内存泄漏的方法</strong></h2>    <ol>     <li>尽量不要让静态变量引用Activity</li>     <li>使用WeakReference弱引用,会保证GC时会被回收</li>     <li>使用静态内部类来代替内部类,静态内部类不持有外部类的引用</li>     <li>静态内部类使用弱引用来引用外部类</li>     <li>在声明周期结束的时候释放资源</li>    </ol>    <h2><strong>5. 减少内存使用</strong></h2>    <ol>     <li> <p>使用更轻量的数据结构(SpareArray代替HashMap)</p>      <ul>       <li>Google自己定义的类占用内存更小</li>      </ul> </li>     <li> <p>避免在onDraw方法中创建对象</p>      <ul>       <li>onDraw()方法被频繁调用,在其中创建对象会导致临时对象过多,发生内存抖动</li>      </ul> </li>     <li> <p>对象池(Message.obtain())</p>      <ul>       <li>当一定要在onDraw中创建对象,推荐使用对象池</li>       <li>相当于对象缓冲,在创建时查找是否已经存在对象,没有在创建</li>      </ul> </li>     <li> <p>LRUCache</p>      <ul>       <li>大大减少内存使用</li>      </ul> </li>     <li> <p>Bitmap内存复用,压缩(inSampleSize,inBitmap)</p> </li>     <li> <p>StringBuilder</p>      <ul>       <li>代替String,尤其是进行拼接操作时</li>      </ul> </li>    </ol>    <p> </p>    <p>来自:http://www.jianshu.com/p/9fb0a795da93</p>    <p> </p>