一共分为 加载、连接(验证、准备、解析)、初始化。
1、加载
- 通过类的全限定名获取此类的二进制字节流。
- 将字节流的静态数据结构转为方法区的运行时数据结构。
- 在堆内存生成该类的class对象,作为方法区数据的入口。
加载这个过程的可控性最强,我们可以自己写类加载器,定义字节流的获取方式(重写loadClass()方法)。数组类对象不由类加载器,用java虚拟机自动加载。
类加载器:
- BootstrapClassLoader启动类加载器:最顶层的加载类,由c++负责%JAVA_HOME%/lib目录下的jar包和类或被-Xbootclasspath参数指定的路径中的所有类。
- ExtensionClassLoader扩展类加载器:负责加载%JRE_HOME%/lib/ext目录下的jar包和类。或被java.ext.dirs系统变量指定的路径下的jar包。
- AppClassLoader应用程序类加载器:负责加载应用classpath下的jar包和类。
后两个继承自java.lang.ClassLoader。
双亲委派模型:
在类加载时,系统首先判断当前类是否被加载过。已被加载的类会直接返回。加载时,首先把请求委派父类加载器的loadClass()处理,父类加载器无法处理时才有自己来处理,父类加载器为null时使用启动类加载器,因此所有请求最终会传送到顶层的启动类加载器中。
好处
保证了Java程序的稳定运行,可以避免类的重复加载,保证了Java的核心API不被篡改。因为JVM区分类不仅靠类名,不同加载器加载产生的是两个不同的类。
如果不想用双亲委派模型,可以自定义一个类加载器,继承ClassLoader,然后重载loadClass()即可。
2、验证
- 文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验。
- 元数据验证:主对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
- 字节码验证:对数据类型和类的方法分析,保证类的方法在运行时不会做出危害虚拟机安全的事。
- 符号引用验证:在虚拟机将符号引用转化为直接引用的时候,对类自身以外的信息进行校验,确保解析动作能够完成。
对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用-Xverfity:none来关闭大部分的验证。
3、准备
准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。需要注意两点也就是类变量和初始值两个关键词:
(1)类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中;
(2)这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值。
4、解析
解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。什么是符号应用和直接引用呢?
符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)
直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
5、初始化
在Java中对类变量进行初始值设定有两种方式:
①声明类变量是指定初始值
②使用静态代码块为类变量指定初始值
JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
类的主动使用
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
- 创建类的实例,也就是new的方式;
- 访问某个类或接口的静态变量,或者对该静态变量赋值;
- 调用类的静态方法;
- 反射(如Class.forName(“com.Test”));
- 初始化某个类的子类,则其父类也会被初始化;
- Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类。