我们的代码是如何运行起来的

image.png

  • 整体看是将java文件打包成jar或者war,然后部署在tomcat等容器或者使用java -jar命令执行
  • 打包成的jar或者war其实就是.class文件,从java文件到class文件的过程就是编译。
  • 当使用java -jar命令来运行字节码文件时实际上是启动了jvm进程。这个jvm进程会来运行这些class文件。
  • jvm进程会运行class文件,但是这些class文件时如何进入jvm进程的呢?这就是需要使用到类加载器来将这些class文件加载到jvm进程中。
  • 通过类加载器将class文件加载到jvm进程中,使用一个抽象的Class类对象来表示,不过如何运行加载后的类呢?这时候就需要使用到字节码执行引擎来执行已经加载到jvm中的类。

    类加载机制

    jvm在什么情况想会加载一个类

*.java文件会被编译成*.class文件,然后jvm会去加载class文件到jvm内存中,这样才可以使用类,关键的问题是什么时候才会将一个**class**加载到**jvm**内存中呢?答案就是什么时候使用什么时候加载。比如对于普通的java应用,jvm启动的时候就会找main方法执行,所以main方法所在的类肯定一开始就要被加载到jvm内存中,在main方法中肯定会使用其他的类,这样又会不断的去加载其他的类到jvm内存中。

类从加载到使用的过程

image.png
注:

  • 加载、验证、准备、初始化这四个阶段的开始顺序是固定的,解析阶段可能发生在初始化之前也可能发生在初始化之后,这是为了支持java语言的运行时动态绑定。
    • 上面说的是开始顺序,而不是运行或者完成顺序,因为开始了可能几个阶段会混合运行。

      每一个加载阶段

      加载阶段

      加载阶段是整个类加载过程中的一个阶段。

      什么时候加载

      简单理解可以认为当使用到一个类的时候就会将类加载到jvm内存中。

加载的具体过程

  1. 通过一个类的权限的类名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

    验证阶段

该阶段主要是为了安全,不能加载可能会威胁jvm自身安全的文件名到内存中。整体上看有以下几个阶段的验证。

文件格式验证

  • 在文件格式上要符合一个描述java类型信息的要求。
  • 该阶段的验证是基于二进制字节流进行的,只有通过该阶段的验证之后,这一段字节流才会被允许进入到java虚拟机内存的方法区进行存储。

    元数据验证

    该阶段主要是对字节码描述的信息进行语义分析,比如该类是否有父类,该类的父类是否继承了不允许被继承的类等等。要保证不存在与《java语言规范》定义相悖的元数据信息。

    字节码验证

    该阶段主要是对类的方法体进行验证,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。

    符号引用验证

  • 该阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候

  • 符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验。

总结:上面四个校验阶段顺序是有规律的,可以认为是从外到内的一个校验。符合我们平时的认知。这里是从文件类型到文件外层结构到内层方法再到运行转化的一个顺序来校验的。
image.png

准备阶段

准备阶段是正式为类中定义的变量 (静态变量,被static修饰的变量)分配内存并且设置变量初始值的阶段。

  • 从概念上讲,变量所使用的内存应该在方法区进行分配,不过方法区本身是一个逻辑上的区域。JDK7之前HotSpot使用永久代来实现,JDK8及以后,类变量会随着Class对象一起放在堆中。
  • 只是为类变量设置初始化,而不是为成员变量设置初始化。
  • 变量初始化值指的是对应类型的零值。比如下面的代码中,在准备阶段之后value的值是0而不是1.
    1. private static int value = 1;
    基本数据类型的零值
数据类型 零值 数据类型 零值
int 0 boolean false
long 0L float 0.0f
short (short)0 double 0.0d
char ‘\u0000’ reference null
byte (byte)0

初始化阶段

  • 在准备阶段已经给类变量进行了一次默认的赋值,而初始化阶段可以认为是要给变量通过我们的代码来进行赋值了。这一次就是你看到的变量是赋的什么值就是什么值。
  • 初始化阶段就是执行类构造器<clinit>()方法的过程,该方法不是程序员在java代码中直接编写的方法,而是javac编译器的产物。

类加载器和双亲委派机制

类加载器

启动类加载器

Bootstrap ClassLoader , 加载安装的jdk目录下lib目录的类

扩展类加载器

Extension ClassLoader, 加载安装的jdk目录下lib\ext目录的类

应用程序类加载器

Application ClassLoader , 加载ClassPath下面的类,简单理解就是加载我们自己写的类。

自定义类加载器

上面三种类加载器是jdk提供的,如果我们想要实现自己的类加载器,可以继承应用程序类加载器来实现我们自己的类加载器。

双亲委派机制

image.png

  • 双亲委派机制就是一个类会先让父类去加载,只有父类加载不到的时候才会让子类去加载。