但程序需要使用某个类且该类还没被加载到内存中,JVM会通过加载、链接、初始化三个步骤对该类进行初始化然后使用。所以这三个步骤也被称为类初始化。
一.类加载过程
1.加载
加载指的是将java程序的class字节码文件读取到内存中,并为其创建一个java.lang.Class对象。
类的加载由类加载器完成。开发者课通过继承ClassLoader来创建自己的类加载器。
通过不同的类加载器可以读取不同来源的class文件。
不是所有类等到被使用时才会被加载,jvm会预先加载一些类
2.链接
类被加载后,系统会为其生成一个对应的Class对象,并进入链接阶段,将把类的字节码数据合并到JRE文件中。链接分三个步骤。
-
验证:检验目标类的内部结构是否合法正确。目的是保证当前加载的Class文件是否符合虚拟机要求,不会危害虚拟机自身安全(包括文件格式验证、元数据验证、字节码验证、符号引用验证)。
文件格式验证:验证字节流是否符合class文件规范。
元数据验证:验证是否符合java语言语法规范。
字节码验证:分析数流和控制,确定语义是否合法,符合逻辑。例如死循环在这关被验证不合法。
符号引用验证:保证引用一定会被访问到。 -
准备:为静态变量分配内存,并设置初始值。
-
解析:将符号引用替换为直接引用,即将由字面变量引用到目标内存的形式变为指针这种能够直接进行定位的句柄。
3.初始化
为类的静态变量赋予初值。
二. 类加载时机
1.new 实例的时候
2.访问某个类的静态变量
3.调用某个类的静态方法
4.java反射:Class.forName("classpath")
5.初始化类的子类
6.JVM启动时标明的启动类
注意
对于类似于final静态变量这种在编译时即可确定的变量,在编译时会在这个变量出现过的地方直接替换为对应值。而使用这样的静态变量不会导致类的初始化。
三. 类加载器
类加载器加载的所有类都会为其生成一个java.lang.Class实例对象。同一个类只会被加载一次,而java.lang.Class对象相当于唯一标识符(全限定类名)。
JVM有三种类加载器:
- 根类加载器(bootstrap class loader)(C++语言实现):加载java核心类(java环境中指定的以及jdk提供的核心jar包类)。
- 扩展类加载器(extensions class loadere):负责jre拓展目录,加载一些我们指定加载的jar包库中的类。
- 系统类记载器(system class loader):负责jam启动时加载-classpath选项对应的JAR包和类路径。也是我们一般可以作为自定义加载器的父类加载器(classLoader),如果我们没有定义自定义类加载器,则默认用该类加载器。
加载器加载步骤:
1.检测此类是否加载过,即内存是否有此Class,没有就下一步
2.查看有无父加载器,无下一步
3.通过根加载器加载
4.失败则抛出ClassNotFoundException异常
5.如无异常,之前每一步都返回java.lang.Class对象
四. 类加载机制
双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器不是以继承的关系来实现,而是通过组合关系来复用父加载器的代码。
双亲委派模型的工作过程为:如果一个类加载器收到了类请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一层都是如此,因此所有类加载的请求都会传到启动类加载器,只有当父加载器无法完成该请求时,子加载器才去自己加载。