目录
一、类加载过程
?1.1 加载(Loading)
1.2 链接(Linking)
(1) 验证(Verify)
(2) 准备(Prepare)
(3) 解析(Resolve)
1.3 初始化(Initialization)
二、类加载器
2.1 引导类加载器(Bootstrap ClassLoader)
2.2 扩展类加载器(Extension ClassLoader)
2.3 系统类加载器(Application ClassLoader)
三、双亲委派机制
四、类加载时机
????????当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。?
?
一、类加载过程
1.1 加载(Loading)
????????加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识(CA FE BA BY),让类加载器来识别。
加载的过程:
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
1.2 链接(Linking)
(1) 验证(Verify)
验证阶段用于检验被加载的类是否有正确的内部结构,确保Class文件字节流中包含信息符合当前虚拟机的要求,保证被加载类的正确性,不会危害虚拟机自身安全。
主要包括四种验证:
? ? ? ? 文件格式验证、元数据验证、字节码验证、符号引用验证
(2) 准备(Prepare)
类准备阶段负责为类的静态变量分配内存,并设置默认初始值,即零值。
这里不包括用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化。 也不会为实例变量初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
(3) 解析(Resolve)
将常量池中的二进制数据中的符号引用替换成直接引用的过程。 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。
1.3 初始化(Initialization)
初始化阶段就是执行类构造器方法<clinit>()的过程。(此方法不同于类的构造器,类的构造器是虚拟机视角下的<init>()方法)。即:
- <clinit>():静态属性加载(static作用的类变量和static代码块)
- <init>():类的构造器加载
变量的赋值过程:在linking中的prepare操作中会先将类变量默认赋值为0,Initialization操作会按照语句在源文件中的顺序重新为其赋值。
注: ? ? ? ? 1. 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕(即先执行父类,再执行子类) ? ? ? ? 2. 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁
二、类加载器
注:
- 可以通过 类加载器对象.getParent() 方法获取上层加载器的对象(但获取不到引导类加载器的对象,因为引导类加载器使用c和c++编写,我们无法得到其对象)。
- Java的核心类库都是使用引导类加载器(Bootstrap ClassLoader)进行加载的。eg:String类
- 对于用户自定义类来说,默认使用系统类加载器进行加载。
2.1 引导类加载器(Bootstrap ClassLoader)
由C/C++语言编写,不是ClassLoader子类。它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader,负责加载JAVA_HOME中jre/lib/rt.jar里所有的class。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
引导类加载器可以加载扩展类和应用程序类加载器,并为他们指定父类加载器。 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
2.2 扩展类加载器(Extension ClassLoader)
由Java语言编写,派生于ClassLoader类,父类加载器是null。它从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR文件放在此目录下,也会自动由扩展类加载器加载。
2.3 系统类加载器(Application ClassLoader)
派生于ClassLoader类,父类加载器是扩展类加载器。负责加载环境变量classPath或系统属性java.class.path 指定路径下的类库。
该类加载 是程序中默认的类加载器,一般来说java应用的类都是由他来完成加载。通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器。
获取类加载器的几种方法:
方式一:获取当前类的ClassLoader
class.getClassLoader();
方式二:获取当前线程上下文的ClassLoader
Thread.currentThead().getContextClassLoader();
方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader();
方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader();
三、双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将他的class文件加载到内存生成class文件。而且加载某个类的class文件时,Java虚拟机采用的双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
?
?
?双亲委派机制的优点:
- 避免类的重复加载
- 保护程序安全,防止核心API被随意篡改。对核心源代码的保护,成为沙箱安全机制。
四、类加载时机
Java程序对类的使用方式分为:主动使用和被动使用。区别在于会不会导致类的初始化(Initialization)。
主动使用:
- 创建类的实例,即new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射主动加载(比如:class.forName("com.Test"))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类
- JDK 7 开始提供的动态语言支持
除了以上七种情况,其他使用Java类的方式都是被看作对类的被动使用,都不会导致类的初始化
补充:
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
- 类的完整类名必须一致,包括包名
- 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。
JVM必须知道一个类型是由启动类加载器加载的还是用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类的类加载器是相同的。
|