编辑

编辑.java文件,即源代码。

编译

由JAVA编译期将.java源代码编译成.class字节码文件,.class字节码文件才是虚拟机可执行的。

加载

得到了.class字节码文件后,当运行这个编译好的字节码文件,系统会启动一个JVM进程,从classpath路径中找到这个字节码文件,进而将其加载到JVM内存中。加载的过程包括三步:加载-连接-初始化。其中连接过程又包括验证、准备、解析三步。

加载

  1. 通过全类名来获取此类的.class字节流文件。
  2. 将这个字节流中的静态存储结构存储到为方法区的运行时数据结构。
  3. 在虚拟机内存中(具体而言是堆中)生成一个这个类的java.lang.Class(运行时类)对象,作为方法区这个类的各种数据的访问入口。

    类加载的时机

    创建类的实例,也就是new一个对象
    访问某个类或接口的静态变量,或者对该静态变量赋值
    调用类的静态方法
    反射(Class.forName("com.lyj.load"))
    初始化一个类的子类(会首先初始化子类的父类)
    JVM启动时标明的启动类,即文件名和类名相同的那个类

    类加载器

    把实现类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作的代码模块称为“类加载器”。类加载器的工作是将 class 文件二进制数据放入方法区内,然后在堆内(heap)创建一个 java.lang.Class 对象,Class 对象封装了类在方法区内的数据结构,并提供了访问方法区内的数据结构的接口。
    注意: 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。这意味着即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类也不相等。

    Bootstrap类加载器

    Bootstrap类加载器用来加载Java的核心类$JAVA_HOME中jre/lib/rt.jar里所有的class。由C++ 实现,不继承自java.lang.ClassLoader,是虚拟机自身的一部分,如果获取它的对象,将会返回null。除了此加载器外别的加载器均来自虚拟机外,可以被用户使用。

    扩展类加载器

    扩展类加载器负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现。

    系统/应用类加载器

    它负责加载用户类路径(ClassPath)上所指定的类库,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。系统/应用类加载器对象可以由ClassLoader类的静态方法getSystemClassLoader()方法的返回。

    类加载的机制

  • 全盘负责
    当一个类加载器负责加载某个类时,该类所依赖和引用其他类也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
  • 缓存机制
    所有被加载过的类都会被缓存。当程序需要用到某个类的时候,首先从缓存区中找有没有该类的Class对象。只有当不存在时,才会读取该类的.class字节码文件加载到内存中,生成新的Class对象,并将其缓存。所以当修改了类信息后要重启JVM才能生效。
  • 双亲委派
    图片说明
    双亲委派机制的工作原理是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

    为什么要双亲委派机制

  1. 可以避免类被重复加载。当父亲加载器已经加载了该类,子加载器就没有必要再进行加载。
  2. 双亲委派模型对于保证Java程序的稳定运作很重要。例如类 java.lang.Object,它存放在 rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。或者通过网络接受了一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。因此双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)。

    验证

    前面讲述了一个类是如何通过字节码文件加载到JVM中的。下面进入到连接阶段。验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

    准备

    准备阶段为类变量(即静态变量)分配内存并设置默认初始值,例如int类型则赋默认初始值0;给静态常量赋初值。所使用的内存都将在虚拟机运行时内存中的方法区中进行分配。

    解析

    解析的过程是将方法区内、运行时常量池中的符号引用替换成直接引用。这一步可以理解成,在将源代码编译成.class文件时,编译器是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,所以只能用符号引用来表示。而符号引用不是真正的引用,不能没有指向引用的对象所在的内存地址,所以解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。

    初始化

    准备阶段已经对类变量(静态变量)和静态常量进行了初始化,但准备阶段的初始化只是赋默认初始值,从而对变量完成了内存分配,才能进行解析。而初始化阶段主要是执行类的初始化方法<clinit()>,类初始化方法是编译器合并类变量赋值动作语句和静态语句块(static{ }语句块)中的语句产生的。这里执行的类变量赋值语句动作则会为变量赋予用户给定的初始值了。如果这个类继承了父类,则先执行父类的类变量赋值动作和静态语句块的内容。

执行

当类成功被加载到虚拟机内存后,就由执行引擎找到main()方法的入口,开始执行其中的指令。