一. 垃圾回收机制

1. 什么是垃圾回收机制

自动内存管理和回收机制。垃圾回收器负责回收程序中已经不使用,但是仍然被各种对象占用的内存。

优点:将程序员从繁重,危险的内存管理工作中解放出来。

缺点:可能会占用大量资源。

2. Android中的垃圾回收机制

Young Generation

  • 大多数新建的对象都位于Eden区。
  • 当EDen区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到一个survivor区。
  • Survivor Space:S0,S1有两个,存放每次垃圾回收后存活的对象。
  • MInor GC同样会检查survivor区中存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。

Old Generation

  • 存放长期存活的对象和经过多次MInor GC后依然存活下来的对象。
  • 满了进行Major GC。

Permanent Generation

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

3. 垃圾回收机制 & FPS

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

二. 内存泄漏

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

当出现以下情况就会发生卡顿。

  • 应用程序分配了大量不能回收的对象。
  • 这导致可分配的内存越来越少。
  • 当新对象的创建需要的内存不够。
  • 当发现内存不够就会调用一次GC进行垃圾回收。

三. 内存抖动

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

  • 瞬间产生大量的对象会严重占用Young Generation的内存区域。
  • 当达到阈值,剩余空间不够,就会触发GC从而导致刚产生的对象又很快被回收。
  • 即时每次分配的对象占用了很少的内存,频分GC叠加在一起会增加Heap的压力从而触发更多其他类型的GC。

结果:这个操作有可能会影响到帧率,并使用户感知到性能问题。

四. 内存检测工具

1.Memeory Profiler 内存监视器

Memeory Profiler是Android Profiler是Android Studio3.0用来替换之前Android Monitor(Allocation Tracker,Heap Viewer)的观察工具,主要用来观察内存,网络,cpu温度。

Memoey Profiler的使用参考:官方文档

2. Leak Canary

官方地址,Leak Canary是Square公司基于MAT开源的一个工具,用来检测Android App中的内存泄露问题。

使用参考文档:LeakCanary

五.常见的内存泄漏问题

1. 单例造成的泄漏

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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. Handler 造成的内存泄漏问题

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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传入

使用前判空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MainActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference mActivity;

private MyHandler(MainActivity activity){
mActivity = new WeakReference(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);
}
}

4. 集合类泄漏

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

六. 避免内存泄漏的方法

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

七. 减少内存使用

  1. 使用更轻量的数据结构(SpareArray代替HashMap)
  2. 避免在onDraw方法中创建对象
    • onDraw()方法被频繁调用,在其中创建对象会导致临时对象过多,发生内存抖动
  3. 对象池(Message.obtain())
    • 当一定要在onDraw中创建对象,推荐使用对象池
    • 相当于对象缓冲,在创建时查找是否已经存在对象,没有在创建
  4. LRUCache
  5. Bitmap内存复用,压缩(inSampleSize,inBitmap)
  6. StringBuilder

参考文章

Android 内存管理


暂停跟新一段时间,找到工作后后续内容:

Android性能优化-布局优化

Android性能优化-电量优化

Android性能优化-网络优化

Android性能优化-Bitmap优化

Android性能优化-其他优化

设计模式:

Android的设计模式-享元模式

Android的设计模式-命令模式