JVM类加载机制

JVM内存模型

JVM内存模型分为5个部分

  • PC寄存器
  • Java虚拟机栈/Java栈
  • Java堆
  • 方法区
  • 本地方法栈

PC寄存器

每一条Java虚拟机线程都有自己的pc寄存器

Java虚拟机栈

每一条Java虚拟机线程都有自己的私有Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧。

Java堆

在Java虚拟机中,堆是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域,GC主要针对区域。Java堆在虚拟机启动时创建。

方法区

在Java虚拟机中,方法区是可供各个线程共享运行时内存区域。方法区存储了每一个类的结构信息,例如,运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容。方法区在虚拟机启动时创建。

本地方法栈

Java虚拟机实现可能会用到传统的栈来支持native方法的执行,这个栈就是本地方法栈,在线程创建时分配。

Java GC

GC(垃圾回收)机制的计算算法是分代收集。GC主要针对的JVM内存模型中的堆区。堆区分为两大部分,新生代和老年代。新生代又分为三个区域,一个eden区,两个survival区。新生代使用复制算法,老年代使用清除标记算法。

JVM类加载

JVM加载机制分为三大部分,加载、连接、初始化

加载阶段

在堆中生成一个代表这个类的java.lang.class对象,在方法区中存储类的信息。

连接阶段

连接又可以分为验证、准备、解析

  • 验证
    验证阶段用于确保类的二进制表示结构上是正确的。

  • 准备
    准备阶段的任务是为类的静态字段分配空间,采用默认值初始化这些字段。

  • 解析
    解析就是把代码中的符号引用替换为直接引用。例如某个类继承了java.lang.Object,原来的符号引用记录的是“java.lang.Object”,并不是java.lang.Object对象,直接引用就是找出对应的java.lang.Object对应的内存地址,建立直接引用关系。

初始化

初始化的过程包括执行类构造器方法,static变量赋值语句,static{}代码块,如果是一个子类进行初始化会先对其父类进行初始化,保证其父类在子类之前进行初始化。

以下几种情况不会执行类初始化:

  • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  • 定义对象数组,不会触发该类的初始化。
  • 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  • 通过类名获取Class对象,不会触发类的初始化。
  • 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  • 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

类加载器

加载器分类

在JVM中有三中类加载器,BootStrap Classloader、Extension Classloader、APP Classloader。

BootStrap ClassLoader主要加载JVM自身需要的类,这个加载器由C++编写是虚拟机的一部分,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的。

Extension Classloader是sun.misc.Launcher中的内部类ExtClassLoader,负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。

APP ClassLoader是sun.misc.Launcher中的内部类AppClassLoader,负责加载用户路径上的类库。

用户也可以通过继承ClassLoader实现自己的类加载器。

双亲委派模式

当一个类加载器接收到类加载的任务时,会首先交给其父加载器去加载,只有当父加载器无法加载时,才会自己加载。其好处是可以避免一个类被重复加载。