当程序主动使用某个类时,如果该类尚未加载到内存中,JVM会通过加载-->连接-->初始化三个步骤将类加载到内存中,并初始化出该类的实例,提供给程序使用。虽然这个过程是三个步骤,但是在JVM中执行时,如果没有意外的话,它是会一下子执行完的。三个步骤的执行顺序如下图所示:
类的加载
类的加载指的是将类的.class文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。程序中使用任何类时,都会为之创建一个java.lang.Class对象放在堆区中,和方法区中的类就好像镜面映射得到的一样。
.class文件在加载的时候,也有几种方式:
● 从本地系统中直接加载
● 通过网络下载.class文件
● 从zip,jar等文件中加载
● 从专有数据库中加载
● 将Java源文件动态编译为.class文件
类的加载是由类加载器完成的,类加载器通常是由JVM来提供的,程序员也可以创建自己的类加载器,但是一定要通过继承ClassLoader的方式来创建。JVM自带的加载器有三种,根类加载器、扩展加载器和系统加载器。
根类加载器(Bootstrap):该类加载器是由C++实现的,在Java代码中无法查看该类。
扩展加载器(Extension):该类加载器是由Java代码实现的,程序员可在程序中查看。
系统加载器(System):又称应用加载器,是使用Java代码实现的。
在JVM中,类加载的过程采用的是父亲委托机制,在这种机制中,除了JVM自带的跟类加载器外,其余的类加载都有且只有一个父加载器,包括用户自定义加载器。例如,当Java程序请求loader加载器加载某个类时,该加载器首先委托它的父加载器来加载,父加载器又会委托它的父加载器,依次类推,只到委托到根类加载器。如果这些父加载器都无法加载这个类,那么才会由loader这个加载器加载这个类。如果都无法加载,则会报出ClassNotFound的Exception。在父亲委托机制中,各加载器按照父子关系形成了树形结构,如下图所示,但父子加载器之间并非一定是继承关系:
类的连接
类的连接阶段负责把类的二进制数据合并到JRE中,该过程又分为三个阶段:
验证:验证过程是确保被加载类的正确性,包括四方面的检查:
1、类文件结构检查:确保程序使用的是Java类文件的固定格式。
2、语义检查:确保类符合Java语言的语法规定。
3、字节码验证:确保字节码流可以被JVM安全执行。
4、二进制兼容验证:相互调用的类之间的协调一致。
准备:JVM为类的静态变量分配内存,并设置默认的初始值,例如为int类型的静态变量分配4个字节的内存空间,并默认赋值为0。
解析:JVM会把类的二进制数据中的符号引用替换为直接引用。
类的初始化
类的初始化就是为类的静态变量赋予正确的初始值,例如如下代码:
public class Test{在验证步骤的准备过程,JVM会为变量a分配内存,然后默认赋值为0,而在类的初始化这个步骤,则是将2这个值赋给该变量。
static int a = 2;
}
所有的JVM实现,必须在每个类或接口在Java程序“首次主动使用”时才会初始化他们,那么什么是主动使用呢?主动使用有下面六种情况:
1、创建类的实例(new Class())
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用类的静态方法
4、反射(如Class.forName(“com.tgb.Test”))
5、初始化某个类的子类
6、JVM启动时被标明为启动类的类
总结
类的加载过程除了上述的三个基本步骤外,还有一些需要注意的小点。多了解JVM的工作机制,对自己代码的质量也有很大帮助。上面是根据《Java编程思想》、《疯狂Java讲义》这两个资料了解的,如果理解上有什么问题,还请过路的大牛指正。