Android内存机制

Android 虚拟机在内存分配时会涉及到以下区域:

  • 寄存器:我们在程序中无法控制
  • :存放基本类型的数据和对象的引用。栈的优势是存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
  • :存放用new产生的数据, 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢
  • 静态域:存放在对象中用static定义的静态成员
  • 常量池:存放常量
  • 非RAM存储:硬盘等永久存储空间

  • 方法区:方法区存放的是类信息、常量、静态变量,所有线程共享区域。
  • 虚拟机栈:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,线程私有区域。
  • 本地方法栈:与虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到的Native方法服务。
  • :JVM管理的内存中最大的一块,所有线程共享;用来存放对象实例,几乎所有的对象实例都在堆上分配内存;此区域也是垃圾回收器(Garbage Collection)主要的作用区域,内存泄漏就发生在这个区域。
  • 程序计数器:可看做是当前线程所执行的字节码的行号指示器;如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。

垃圾清理

标记清除

最基础的收集算法:分为“标记”和“清除”两个阶段,首先,标记出所有需要回收的对象,然后统一回收所有被标记的对象。

  • 效率问题,标记和清除两个过程的效率都不高;
  • 空间问题,标记清除之后会产生大量的不连续的内存碎片。
复制算法

将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存将用完了,就将还存活着的对象复制到另一块内存上面,然后再把已使用过的内存空间一次清理掉。 这种方法的特点:

  • 优点:实现简单,运行高效;每次都是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可;
  • 缺点:粗暴的将内存缩小为原来的一半,代价实在有点高。
标记-压缩(整理)算法

先标记需要回收的对象(标记过程与“标记-清除”算法一样),然后把所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

  • 避免了内存碎片;
  • 避免了“复制”算法50%的空间浪费;
  • 主要针对对象存活率高的老年代。
分代收集算法

将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年代则采取标记-压缩算法。

对象是否回收的依据

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用该对象时,计数器值加1;引用失效时,计数器值减1;任意时刻计数器为0的对象就是不可能再被使用的,表示该对象不存在引用关系。 - 优点:实现简单,判定效率也很高; - 缺点:难以解决对象之间相互循环引用导致计数器值不等于0的问题。

可达性分析算法

以一系列成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达),则证明此对象是不可用的。

从root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象

GC时机

  • 显式的调用System.gc()
  • 内存分配失败时 或 内存不足
  • 如果分配的对象大小超过384KB,运行并发标记

参考文档