我们的代码是如何运行起来的
- 整体看是将
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
内存中。
类从加载到使用的过程
注:
- 加载、验证、准备、初始化这四个阶段的开始顺序是固定的,解析阶段可能发生在初始化之前也可能发生在初始化之后,这是为了支持java语言的运行时动态绑定。
加载的具体过程
- 通过一个类的权限的类名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。验证阶段
该阶段主要是为了安全,不能加载可能会威胁jvm
自身安全的文件名到内存中。整体上看有以下几个阶段的验证。
文件格式验证
- 在文件格式上要符合一个描述java类型信息的要求。
该阶段的验证是基于二进制字节流进行的,只有通过该阶段的验证之后,这一段字节流才会被允许进入到
java
虚拟机内存的方法区进行存储。元数据验证
该阶段主要是对字节码描述的信息进行语义分析,比如该类是否有父类,该类的父类是否继承了不允许被继承的类等等。要保证不存在与《java语言规范》定义相悖的元数据信息。
字节码验证
该阶段主要是对类的方法体进行验证,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
符号引用验证
该阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候
- 符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验。
总结:上面四个校验阶段顺序是有规律的,可以认为是从外到内的一个校验。符合我们平时的认知。这里是从文件类型到文件外层结构到内层方法再到运行转化的一个顺序来校验的。
准备阶段
准备阶段是正式为类中定义的变量 (静态变量,被static修饰的变量)分配内存并且设置变量初始值的阶段。
- 从概念上讲,变量所使用的内存应该在方法区进行分配,不过方法区本身是一个逻辑上的区域。
JDK7
之前HotSpot
使用永久代来实现,JDK8
及以后,类变量会随着Class
对象一起放在堆中。 - 只是为类变量设置初始化,而不是为成员变量设置初始化。
- 变量初始化值指的是对应类型的零值。比如下面的代码中,在准备阶段之后value的值是0而不是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
提供的,如果我们想要实现自己的类加载器,可以继承应用程序类加载器来实现我们自己的类加载器。
双亲委派机制
- 双亲委派机制就是一个类会先让父类去加载,只有父类加载不到的时候才会让子类去加载。