Android 进阶解密笔记-Java 类加载器

2019-12-26 15:00:22 浏览数 (1)

摘抄自<<Android 进阶解密>>一书

Java 跨平台

Java是通过实现不同平台上的虚拟机,然后即时翻译javac生成的中间代码来做到跨平台的。

跨平台的工作被虚拟机开发人员来解决了

Java虚拟机执行流程

分为两大部分:分别是编译时环境与运行时环境,当一个文件经过Java编译后会生成Class文件,这个文件交给虚拟机处理。Java虚拟机与Java语言没有必然联系,虚拟机只与二进制文件:Class文件有关,因此只要任何语言编译成Class文件,就可以被Java虚拟机识别并执行

Class文件格式

生成的Class文件格式不会依赖于特定的硬件和操作系统,每一个Class文件都对应唯一的类与接口定义信息,但是类或者接口并不一定定义在文件中,比如类与接口可以通过类加载器来直接生成。

类生命周期

类生命周期分为加载、链接(验证、准备与解析)、初始化、使用与卸载

  1. 加载:查找并加载Class文件
  2. 验证:确保被导入的类型正确性
  3. 准备:为类的静态字段分配字段,并用默认值初始化这些字段
  4. 解析:虚拟机将常量池内的符号引用替换为直接引用
  5. 初始化:将类变量初始化正确的初始值

加载阶段(不是类加载)主要做了三件事:

  1. 根据特定的名称查找类或者接口类型的二进制字节流
  2. 将这个二进制字节所代表的的静态存储结构转化为方法区运行时的数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口

类加载系统

JavaAndroid类加载器都遵循双亲委托机制

Java 类加载器

Java虚拟机有两种类加载器:系统加载器与自定义加载器

系统加载器包括:

  1. Bootstrap ClassLoader引导类加载器,Java虚拟机启动就是通过引导类加载器创建一个初始类来完成的,由于类加载器是使用平台相关底层C/C 语言实现,不能被Java代码访问到,但是我们可以查询某个类是否被引导类加载器加载过
  2. Extensions ClassLoader扩展类加载器:

用于加载Java扩展类,提供除了系统类之外的额外功能

  1. Application ClassLoader 应用程序类加载器

又称为系统类加载器,因为这个;类加载器可以通过ClassLoadergetSystemClassLoader方法获取到。

ClassLoaer 的加载机制是一种特别聪明的方式,双亲委托机制,在这种机制下,一个Class只会被加载一次。

所谓双亲委托机制就是首先判断该类是否被加载过,如果没有加载则不是自身去查找而是委托给父类加载器进行查找,这样依次进行递归,直到委托给最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该class就会直接返回,如果没有找到,则会继续向下查找,如果还没有找到最好交由自身去查找。Bootstrap ClassLoaderc 代码实现的加载器,Java中无法访问。

ClassLoader父子关系并不是使用继承来实现的,而是使用组合来实现代码的复用。

双亲委托机制优点:

  1. 避免重复加载,如果已经加载过,就不会再次加载,而是直接读取已经加载的Class
  2. 更加安全。如果不使用双亲委托机制,就可以自定义一个String类来代替系统的String类,会造成安全隐患,采用双亲委托机制会使系统的Sting在虚拟机启动时候就加载,无法自定义string类,除非修改类加载器搜索算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为是同一个类,否则不是。

Android 类加载器

  1. BootClassLoader Android系统启动时会使用该类加载器来加载常用类,与SDKBootstrap classLoader不同,它并不是C 代码实现,而是Java代码实现。它是ClassLoader内部类,继承自ClassLoader。是一个单例类,访问修饰符是默认的,只有在同一个包中才可以访问,因此应用程序中无法直接调用。BootClassLoader是在Zygote进程的Zygote入口方法中被创建,用于加载preloaded-classes文件中存有的预加载类。
  2. DexClassLoader 它可以加载dex文件以及包含dex的压缩文件(apkjar文件),不管加载那个文件,最终都要加载dex文件。它继承自BaseDexClassLoader,方法都在BaseDexClassLoader中实现的
  3. PathClassLoader Android系统使用PathClassLoader来加载系统类和应用程序的类。也继承自BaseDexClassLoader,都在BaseDexClassLoader中实现的,它无法定义解压dex文件的存储路径,因此PathClassLoader用来加载已经安装apkdex文件。安装的apkdex文件会存储在/data/dalvik-cache中。PathClassLoader是在SystemServer进程中采用工厂模式创建的。

对象

对象创建

  1. 判断对象对应的类是否加载、链接与初始化

虚拟机接收到一条new指令时,首先会检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表类是否已被加载、链接与初始化过

  1. 为对象分配内存,有两种方式:
  • 指针碰撞:如果Java堆内存是规整,即所有用过的内存放在一边,而空闲内存放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样来完成分配内存操作
  • 空心列表:如果Java堆内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存时可用的,这样分配时,从列表查询足够大的内存分配给对象,并在分配后更新列表
  1. 处理并发安全问题,有两种方式:
  • 对分配的内存空间动作进行同步处理,比如在虚拟机采用CAS算法并配上失败重试的方式保证更新操作的原子性
  • 每个线程在Java堆中预先分配一小块内存,这块内存称为本地线程分配缓冲,线程需要分配内存时,就在对应的缓冲上分配内存
  1. 初始化分配的内存空间

将分配到的内存,除了对象头外都初始化为零值

  1. 设置对象的对象头

将对象的所属类、对象的hashcode和对象的GC分代年龄等数据存储在对象的对象头中

  1. 执行init方法进行初始化

初始化成员变量、调用类的构造方法,这样一个对象就被创建出来了

对象的堆内存布局

分为三个区域:

  1. 对象头:包括两部分信息,分别是Mark Word与元数据指针,Mark Word用于存储对象运行时的数据,比如HashCode、锁状态标志、Gc分代年龄、线程持有的锁等,而元数据指针用于指向方法区中的目标类的元数据,通过元数据可以确定对象的具体类型。
  2. 实例数据:用于储存对象中的各种类型字段信息(包括从父类继承来的)
  3. 对其填充:不一定存在,没有实际意义,只是起到占位作用

Java 对象在虚拟机中的生命周期

  1. 创建阶段:
  • 为对象分配存储空间
  • 构造对象
  • 从超类到子类对static成员进行初始化
  • 递归调用超类的构造方法
  • 调用子类的构造方法
  1. 应用阶段

当对象被创建,并分配给变量赋值时,状态就切换到了应用阶段。这个阶段对象至少具有一种引用强,软,弱,虚引用

  1. 不可见阶段

在程序中找不到对象的任何强引用,比如程序执行已经超出了对象的作用域。在不可见阶段,对象仍可能被特殊的强引用GC Roots持有着,比如对象被本地方法栈中的JNI引用或被运行中的线程引用等

  1. 不可达阶段

在程序中找不到对象的任何强引用,并且垃圾收集器发现对象不可达

  1. 收集阶段

垃圾收集器已经发现对象不可达,并且垃圾收集器准备好对该对象的内存空间重新分配,如果对象重写finalize方法,就会调用该方法

  1. 终结阶段

在对象执行完finalize方法仍然处于不可达,或者对象没有重写finalize方法,则对象进入终结阶段,并等待垃圾收集器回收对象空间**

  1. 对象空间重新分配阶段

当垃圾收集器对对象的内存空间进行回收或者再分配时,这个对象就会彻底消失

被标记不可达的对象会被垃圾收集器立即回收

不会,被标记对象会进入收集阶段,如果该对象重写了finalize方法,就调用,否则进入终结阶段,这时才会被垃圾收集器回收。

0 人点赞