一、需求
我司存在内存为1G RAM的设备,属于低内存设备,经常会出现内存很紧张的场景,也容易因此导致一系列七七八八的边际问题,故有必要了解Android系统的内存相关知识:
- 了解内存的分配、回收方式
- 了解OOM、LMK的相关机制
- 了解Android系统内存相关调试方式
- 了解Android系统的性能优化方案
二、环境
- JDK 1.8
- Android 10
三、JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一个虚构出来的计算机,有着自己完善的硬件架构,如处理器、堆栈等。
3.1 编译&执行过程
Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java文件必须先通过一个叫javac的编译器,将代码编译成class文件,然后通过JVM把class文件解释成各个平台可以识别的机器码,最终实现跨平台运行代码。

3.2 JVM内存模型

3.2.1 方法区
方法区是《Java虚拟机规范》中规定的一个内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。方法区是一个规范,它的实现取决于不同的虚拟机:
- 在Java8之前,HotSpot虚拟机使用 永久代 来实现方法区。

- 而Java8之后,HotSpot虚拟机使用 元空间 来实现方法区。

方法区存储的信息如下:
名称 | 内容 |
---|---|
类型信息 | (1)是类class、接口interface、枚举enum、注解annotation中哪一种 (2)完整有效名称(包名.类名) (3)直接父类的完整名称(接口和java.lang.Object没有父类) (4)类型的修饰符(public、abstract、final等) (5)类型直接接口的有序列表(实现的接口构成列表) |
域(Field、属性)信息 | (1)保存类型所有域(属性)的相关信息和声明顺序 (2)相关信息包含:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient等) |
方法(method)信息:按顺序保存 | (1)方法名称 (2)返回类型(含Void) (3)方法参数和类型(按顺序) (4)方法的修饰符(public、private、protected、static、final、synchronized、native、abstract) (5)方法的字节码、操作数栈、局部变量表及其大小(abstract和native方法除外) (6)异常表abstract和native方法除外),每个异常处理开始、结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引等 |
Non-final类变量:(static修饰的变量,静态变量) | (1)逻辑上是类数据一部分 (2)在类的加载过程中链接的准备阶段设置默认初始值,初始化阶段赋予真实值 (3)类变量(non-final)被所有实例共享,没有实例化类对象也可访问,(全局常量,static和final一起修饰) (4)与final修饰的类变量不同,每个全局常量在编译时就分配了 |
Class文件常量池 | (1)一个有效的字节码文件除了包含类的版本信息、字段、方法以及接口描述信息外,还包含一个常量池表。常量池表中包含字面量、域和方法的符号引用。 (2)字面量就是int i=5;String=”Hello World!”中的5和”Hello World!” (3)一个JAVA源原文件中的类、接口,编译后生成字节码文件,Java中的字节码需要数据,但是这些数据很多很大,不能直接存到内存中,可以将其存到常量池中,字节码中包含了指向常量池的引用。 (4)常量池中包含:数量值、字符串引用、类引用、字段引用、方法引用 |
运行时常量池 | (1)运行时常量池是方法区的一部分 (2)常量池表示Class中的一部分,用于存放编译器生成的各种字面量和符号引用,在加载类和接口到虚拟机后,就创建相应的运行时常量池 (3)JVM为每个加载的类或接口维护一个运行时常量池,池中数据类似数组项,通过索引访问 (4)运行时常量池中含多种不同常量,包含编译器就明确的数值字面量,也包含运行期的方法或者字段引用,此时不再是常量池中的符号地址,而是真实地址。 (5)运行时常量池,相对于Class文件中的常量池,还有一个特征就是具备动态性,可以动态添加数据到运行时常量池 (6)当创建运行时常量池时,如果所需内存空间大于方法区能提供的最大值,那么JVM抛出OutOfMemoryError异常 |
3.2.2 堆
堆是java内存管理中最大的一块内存,也是所有线程共享的一块内存,在虚拟机启动时创建。堆中主要存放的是对象实例、数组。几乎所有的对象实例、数组都在这一块内存中分配。
堆也是GC垃圾回收的主要区域。垃圾回收现在主要采取的是分代垃圾回收算法。为了方便垃圾回收,java堆还进行了细分:新生代(YoungGen)、和老年代(oldGen),默认占比为:1:2;其中新生代还可以划分为Eden空间、survivor0空间、survivor1空间,默认占比为:8:1:1;

对象内存分配过程如下:
1.new一个对象value,value先放于新生代->Eden区;
2.当Eden区空间填满后,我们需要再创建value2对象,JVM会对Eden区继续垃圾回收(Minor GC);
3.Eden区触发GC后,Eden区会被清空,同时Eden区幸存对象会移动到S0幸存区。此时,Eden区和S1区未存放对象;
&nb