Android 性能优化 -- 内存
APP 进程一般只分配 16M,32M,64M 的内存空间,C和C++会自己分配内存(malloc)和释放内存(free),而Java不是,So….
内存分配策略:
静态存储区:内存空间在程序编译(类加载)的时候就分配好,这块内存在程序整个运行都一直存在。主要存放静态数据,全局static数据和一些常量。
栈式存储区:在执行方法时,方法内部变量存储放在栈上创建,方法执行结束后背释放。栈内存分配速度快,容量有限(由操作系统决定),数据先进后出,进出不产生内存碎片,运行效率高且稳定。
堆式存储区:动态内存分配,有时候可以用malloc或者new()来申请分配一个内存。类里的对象引用也是在堆内存中。(堆是一个不连续的存错区域)
JAVA 内存回收机制(GC)
- 基本原理:JAVA 虚拟机通过算法必须追踪运行程序中有用的对象,而且最终释放没用的对象,垃圾回收期从根开始可达对象都是活动对象,包括间接可达。不可达就是垃圾回收的对象。
- 标记-清除法:标记需要清除的对象,然后清除。效率不高,产生不连续内存碎片。
- 复制算法:将内存区域分为两块,每次使用一块,当这一块用完了,将存活的复制到另外一面墙,清理完一面墙。代价内存缩小一半,没有内存碎片,效率低。
- 标记-压缩算法:标记过程与“标记-清除”一样,但后续不是直接清除,而是所有对象移动到一端,然后清理掉边界以外的内存。
- 分代收集法:分为新生代(生成的对象首先都是放在年轻代的),老年代(生命周期长的对象,年轻代经历N次回收还存活的对象),持久代(存放静态文件,java类,方法等)。新生代有大批对象死亡,采用复制算法。老年代存活率高,就必须采用标记-清理或标记-压缩来回收。
内存泄露与内存溢出
内存泄露:指程序已经终止,但是一些临时对象,对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。
内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。或者说,当应用占用的堆内存资源超过了Dalvik虚拟机分配的内存就会内存溢出.
堆内存&栈内存
Java 中数据存储的位置其实还包括寄存器(无法人为控制,自动分配),常量池(字符串常量和基本类型常量),非RAM存储区(流对象和持久化对象)。堆内存和栈内存都是RAM(内存)上存储。
- 栈内存速度大于堆内存
- 堆内存灵活性大于栈内存
- 栈可以实现数据共享。 (int a = 100 ,int b =100)
- new 出来的都是堆内存,其余都是栈内存(不是很严谨)BirthDate d1= new BirthDate(1,1,1); d1对象为引用在栈中,new BirthDate()在堆中。
Java 常见的引用
在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些占用内存大对象具备一定的生命周期的话,可以使用SoftReference和WeakReference就能很好防止内存溢出
引用名称 | 回收时机 | 生命周期 | 使用 |
---|---|---|---|
StrongReference(强) | 从不回收 | JVM停止的时候终止 | 用于对象的保存 |
SoftReference(软) | 内存不足时 | 内存不足时终止 | 结合ReferenceQueue构造有效期短 |
WeakReference(弱) | 垃圾回收时 | GC后终止 | 同软引用 |
PhatomReference(虚) | 垃圾回收时 | GC后终止 | 结合ReferenceQueue跟踪对象被回收活动 |
- LRU 算法和SoftReference区别:SoftReference回收量比较大,而且不能控制回收哪个对象,LRU 算法是回收最近不常使用的对象.
Android 找内存泄露的一般步骤
确定是否存在内存泄露
- Android monitor 某个操作前,GC完后与动作发生后的内存大小是否一致;
- MAT 中Total Size值都会稳定在一个有限的范围内,随着操作次数的增大,Total Size变大,证明操作导致内存泄露
找出怀疑对象
- MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象
MAT分析hprof定位内存泄露
- 一般性步骤Histogram -> List Objects -> with incoming references -> Merge Shortest Paths to GC Roots -> exclude all phantom/weak/soft etc.references
Android 中常见到内存泄露案例
单例模式Context的使用不正常
如果传入的是Activity的Context,当Activity退出时候,因为单例对象持有该Activity的引用,所以Activity退出时内存并不会回收.
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}非静态内部类直接在类中创建实例
1
2
3
4
5
6
7
8
9
10
11public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
protected void onCreate(Bundle savedInstanceState) {
if(mManager == null){
mManager = new TestResource();
}
class TestResource {
}
}这样非静态内部类会默认持有外部类的引用,该实例生命周期和应用一样长,导致了该实例会一直持有Activity的引用,导致Activity内存资源不能回收泄露。
匿名内部类线程造成的泄露
匿名内部类异步任务AsyncTask和Thread,在Activity销毁之前,任务还未完成,将导致Activity内存资源无法回收,内存泄露,正确做法是使用静态内部类。
资源未及时关闭回收
在用到如下功能时候BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap,自定义属性attribute。记得Activity销毁时或者不适用的时候回收资源或者注销。
监听未移除
最常见的就是addOnWindowFocusChangeListener,一定要在onWindowFocusChanged()回调中移除掉。
无限轮播的动画
没有在onDestory()中停止动画,导致Activity变成泄露的对象
常见内存优化工具
Heap Viewer工具 , Heap Snapshot工具 , MAT , TraceView , Allocation Tracker工具。