Android 应用运行在 Java 虚拟机上,每打开一个 Android app 都会打开一个独立的虚拟机。运行虚拟机时会在设备的寄存器上开辟一块内存空间作为虚拟机的内存区域。虚拟机的内存区域一般划分为堆、栈、方法区/静态存储区、运行时常量池、本地方法栈等。
堆、栈
堆是不连续的内存区域(因为系统是用链表来存储堆中的空闲内存地址)。堆上分配内存的过程为动态分配过程,在堆中存放由 new 关键字创建的对象或数组。堆的内存管理是由 Java 的 GC(垃圾回收机制)来管理的。通常我们所说的内存优化即是堆内存的优化。
栈是针对线程来说的,每个线程都有一个栈。栈中主要存放基本类型的变量和对象的引用,并且对象本身是没有存放在栈中,而是存放在堆中的。
当方法执行时,方法内部的局部变量被创建,基本类型的变量会直接存放在栈中,引用类型的变量则会将该引用变量在堆中的内存地址存放在栈中。方法执行结束时,这些局部变量在栈中所占的内存空间就会被释放。
举个例子:
public class Person {
int i = 1;
Girl g1 = new Girl();
public void x() {
int j = 1;
Girl g2 = new Girl();
Girl g3 = g1;
}
}
i
、g1
为成员变量,j
、g2
、g3
为方法x()
中的局部变量,i
和g1
字段属于类,类最终是要被new出来。所以i
和g1
存放在堆中的。j
为基本类型,作为局部变量肯定是存放在栈中的。g2
和g3
都为引用类型,它们引用的对象实体是存放在堆中的,在栈中只存有他们的引用(堆中的内存地址),当方法执行结束后,栈中的引用被释放。
总结: 成员变量(基本类型和引用类型)全部存储在堆中;基本类型的局部变量存储在栈中;引用类型的局部变量的对象实体是存在堆中,栈中只存有它的引用。
垃圾回收器 GC
栈内存的特点是效率高、速度快,并且在方法结束时就自动释放,但是它的容量有限。而堆内存空间的释放完全依赖于 GC 。在程序的运行过程中,GC 会不定时的被唤醒检查是否有没有被引用的对象,并释放他们的空间。
一般在堆内存占用较多(内存不足)的空闲时候系统可能会自动执行垃圾回收。但是垃圾回收机制只针对“垃圾”有效。所谓垃圾,是指存在于堆中,并且程序不能再访问到的对象(不可达的对象)。GC 在执行垃圾回收时,会将不可达的对象放入垃圾回收器中,而对于那些还处于引用状态即可达对象 GC 会根据该对象的引用状态来决定是否回收。
Java中对象的引用状态有强引用、软引用、弱引用、虚引用4种。
强引用
强引用是使用最普遍的引用方式,我们平常使用的大部分引用都是强引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠回收具有强引用的对象来解决内存不足的问题。
软引用
一个对象具有软引用,如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
public class Test {
public static void main(String[] args) {
Person person = new Person();
SoftReference<Person> sr = new SoftReference<Person>(person);
person = null;
if (sr != null) {
person = sr.get();
} else {
person = new Person();
sr = new SoftReference<Person>(person);
}
}
}
弱引用
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class Test {
public static void main(String[] args) {
Person person = new Person();
WeakReference<Person> wr = new WeakReference<Person>(person);
person = null;
if (wr != null) {
person = wr.get();
} else {
person = new Person();
wr = new WeakReference<Person>(person);
}
}
}
虚引用
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动,我们平常一般不会使用。
需要注意的是,GC 什么时候回收垃圾是无法控制的,垃圾回收时间也是无法预料的。
GC 内存管理机制虽然能帮助我们自动管理内存,避免了程序员主动释放内存的繁琐工作,但它不能解决所有的问题。GC不能回收的对象可能会导致内存泄露,甚至使整个程序发生OOM。
内存泄露
当一个对象在程序执行过后已经不需要再使用了,但是有其他的对象仍然持有该对象的引用,以致该对象不能被 GC 回收,那么这个对象会一直占据内存,从而导致该内存不可用。这种本该被GC回收(不再需要用了)而又不能被回收(被其他对象持有引用)以致停留在堆内存中的对象就造成了内存泄露。
内存溢出
内存溢出,OutOfMemory ,即 我们通常所说的OOM,是指程序在申请内存时,没有足够的内存空间供其使用。
在Android中,有以下几种情况可能发生内存溢出:
1.内存泄露可能导致内存溢出的发生;
2.一次加载的数据量过大,如一次从数据库读取过多数据;
3.保存了许多耗用内存过大的对象(如Bitmap)或加载单个超大的图片,造成内存超出限制;
4.代码中存在死循环或循环产生过多重复的对象实体。
GC能自动回收并释放无用的对象,但是如果程序频繁的GC也会导致一些问题。
频繁GC
程序在执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。故而如果Andorid应用频繁GC,UI线程有可能会暂停,从而导致导致界面卡顿。
导致频繁GC主要有两个方面的原因:
1.内存抖动, 即大量的对象被创建又在短时间内马上被释放。
2.瞬间产生大量对象会严重占用内存区域,当达到阀值, 剩余空间不够的时候,就会触发GC。即使每次分配的对象需要占用很少的内存,但是他们叠加在一起会增加堆内存的压力, 从而触发更多的GC。
OK,Android内存优化的概述就到此为止。后面会详细介绍Android中的内存泄露及相关优化。