摘抄自<<Android 进阶解密>>一书
Java 跨平台
Java
是通过实现不同平台上的虚拟机,然后即时翻译javac
生成的中间代码来做到跨平台的。
跨平台的工作被虚拟机开发人员来解决了
Java虚拟机执行流程
分为两大部分:分别是编译时环境与运行时环境,当一个文件经过Java
编译后会生成Class
文件,这个文件交给虚拟机处理。Java
虚拟机与Java
语言没有必然联系,虚拟机只与二进制文件:Class
文件有关,因此只要任何语言编译成Class
文件,就可以被Java
虚拟机识别并执行
Class文件格式
生成的Class
文件格式不会依赖于特定的硬件和操作系统,每一个Class
文件都对应唯一的类与接口定义信息,但是类或者接口并不一定定义在文件中,比如类与接口可以通过类加载器来直接生成。
类生命周期
类生命周期分为加载、链接(验证、准备与解析)、初始化、使用与卸载
- 加载:查找并加载
Class
文件 - 验证:确保被导入的类型正确性
- 准备:为类的静态字段分配字段,并用默认值初始化这些字段
- 解析:虚拟机将常量池内的符号引用替换为直接引用
- 初始化:将类变量初始化正确的初始值
加载阶段(不是类加载)主要做了三件事:
- 根据特定的名称查找类或者接口类型的二进制字节流
- 将这个二进制字节所代表的的静态存储结构转化为方法区运行时的数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口
类加载系统
Java
与Android
类加载器都遵循双亲委托机制
Java 类加载器
Java
虚拟机有两种类加载器:系统加载器与自定义加载器
系统加载器包括:
Bootstrap ClassLoader
引导类加载器,Java
虚拟机启动就是通过引导类加载器创建一个初始类来完成的,由于类加载器是使用平台相关底层C/C
语言实现,不能被Java
代码访问到,但是我们可以查询某个类是否被引导类加载器加载过Extensions ClassLoader
扩展类加载器:
用于加载Java
扩展类,提供除了系统类之外的额外功能
Application ClassLoader
应用程序类加载器
又称为系统类加载器,因为这个;类加载器可以通过ClassLoader
的getSystemClassLoader
方法获取到。
ClassLoaer
的加载机制是一种特别聪明的方式,双亲委托机制,在这种机制下,一个Class
只会被加载一次。
所谓双亲委托机制就是首先判断该类是否被加载过,如果没有加载则不是自身去查找而是委托给父类加载器进行查找,这样依次进行递归,直到委托给最顶层的Bootstrap ClassLoader
,如果Bootstrap ClassLoader
找到了该class
就会直接返回,如果没有找到,则会继续向下查找,如果还没有找到最好交由自身去查找。Bootstrap ClassLoader
是c
代码实现的加载器,Java
中无法访问。
ClassLoader
父子关系并不是使用继承来实现的,而是使用组合来实现代码的复用。
双亲委托机制优点:
- 避免重复加载,如果已经加载过,就不会再次加载,而是直接读取已经加载的
Class
- 更加安全。如果不使用双亲委托机制,就可以自定义一个
String
类来代替系统的String
类,会造成安全隐患,采用双亲委托机制会使系统的Sting在虚拟机启动时候就加载,无法自定义string
类,除非修改类加载器搜索算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java
虚拟机才会认为是同一个类,否则不是。
Android 类加载器
BootClassLoader
Android
系统启动时会使用该类加载器来加载常用类,与SDK
的Bootstrap
classLoader
不同,它并不是C
代码实现,而是Java
代码实现。它是ClassLoader
内部类,继承自ClassLoader
。是一个单例类,访问修饰符是默认的,只有在同一个包中才可以访问,因此应用程序中无法直接调用。BootClassLoader
是在Zygote
进程的Zygote
入口方法中被创建,用于加载preloaded-classes
文件中存有的预加载类。DexClassLoader
它可以加载dex
文件以及包含dex
的压缩文件(apk
与jar
文件),不管加载那个文件,最终都要加载dex
文件。它继承自BaseDexClassLoader
,方法都在BaseDexClassLoader
中实现的PathClassLoader
Android
系统使用PathClassLoader
来加载系统类和应用程序的类。也继承自BaseDexClassLoader
,都在BaseDexClassLoader
中实现的,它无法定义解压dex
文件的存储路径,因此PathClassLoader
用来加载已经安装apk
的dex
文件。安装的apk
的dex
文件会存储在/data/dalvik-cache
中。PathClassLoader
是在SystemServer
进程中采用工厂模式创建的。
对象
对象创建
- 判断对象对应的类是否加载、链接与初始化
虚拟机接收到一条new指令时,首先会检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表类是否已被加载、链接与初始化过
- 为对象分配内存,有两种方式:
- 指针碰撞:如果
Java
堆内存是规整,即所有用过的内存放在一边,而空闲内存放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样来完成分配内存操作 - 空心列表:如果
Java
堆内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存时可用的,这样分配时,从列表查询足够大的内存分配给对象,并在分配后更新列表
- 处理并发安全问题,有两种方式:
- 对分配的内存空间动作进行同步处理,比如在虚拟机采用
CAS
算法并配上失败重试的方式保证更新操作的原子性 - 每个线程在
Java
堆中预先分配一小块内存,这块内存称为本地线程分配缓冲,线程需要分配内存时,就在对应的缓冲上分配内存
- 初始化分配的内存空间
将分配到的内存,除了对象头外都初始化为零值
- 设置对象的对象头
将对象的所属类、对象的hashcode
和对象的GC
分代年龄等数据存储在对象的对象头中
- 执行
init
方法进行初始化
初始化成员变量、调用类的构造方法,这样一个对象就被创建出来了
对象的堆内存布局
分为三个区域:
- 对象头:包括两部分信息,分别是
Mark Word
与元数据指针,Mark Word
用于存储对象运行时的数据,比如HashCode
、锁状态标志、Gc
分代年龄、线程持有的锁等,而元数据指针用于指向方法区中的目标类的元数据,通过元数据可以确定对象的具体类型。 - 实例数据:用于储存对象中的各种类型字段信息(包括从父类继承来的)
- 对其填充:不一定存在,没有实际意义,只是起到占位作用
Java 对象在虚拟机中的生命周期
- 创建阶段:
- 为对象分配存储空间
- 构造对象
- 从超类到子类对
static
成员进行初始化 - 递归调用超类的构造方法
- 调用子类的构造方法
- 应用阶段
当对象被创建,并分配给变量赋值时,状态就切换到了应用阶段。这个阶段对象至少具有一种引用强,软,弱,虚引用
- 不可见阶段
在程序中找不到对象的任何强引用,比如程序执行已经超出了对象的作用域。在不可见阶段,对象仍可能被特殊的强引用GC Roots
持有着,比如对象被本地方法栈中的JNI引用或被运行中的线程引用等
- 不可达阶段
在程序中找不到对象的任何强引用,并且垃圾收集器发现对象不可达
- 收集阶段
垃圾收集器已经发现对象不可达,并且垃圾收集器准备好对该对象的内存空间重新分配,如果对象重写finalize
方法,就会调用该方法
- 终结阶段
在对象执行完finalize
方法仍然处于不可达,或者对象没有重写finalize
方法,则对象进入终结阶段,并等待垃圾收集器回收对象空间**
- 对象空间重新分配阶段
当垃圾收集器对对象的内存空间进行回收或者再分配时,这个对象就会彻底消失
被标记不可达的对象会被垃圾收集器立即回收
不会,被标记对象会进入收集阶段,如果该对象重写了finalize
方法,就调用,否则进入终结阶段,这时才会被垃圾收集器回收。