前言:
jvm包含两个子系统分别是类加载器子系统和执行引擎子系统,接下来介绍类加载器子系统
1、类加载器子系统的作用
-
类加载器子系统负责从文件系统或者网络中加载Class文件,Class文件在文件开头有特定的文件标识。(eg:CA FE BA BY)
-
ClassLoader只负责Class文件的加载,至于他是否可以运行,则是由ExecutionEngine决定。
-
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池的信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。
1.1类加载器ClassLoader
1)class file存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。(实例化出来的实例,存在于堆区,意思是,模板只有一份存在于方法区。就相当于类,一个类就是模板,然后实例化不同的对象)
2)class file加载到JVM中, 被称为DNA元数据模板 放在方法区。
3)在 .class文件->JVM->最终成为元数据模板,此过程就要一个运输工具(类加载器Class Loader),扮演一个快递员的角色。(Class Loader起着将类加载进入jvm的快递作用,快递员身份)
2、类的加载过程
public class HelloLoader {
public static void main(String[] args) {
System.out.println("我被ClassLoader加载了");
}
}
HelloLoader 类加载的过程()
2.1加载
通过一个类的全限定名获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 (hptspot虚拟机)
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口 (在内存中生成类的模板,可以被随时调用,也是反射的原理)
加载class文件的方式
从本地系统中直接加载
通过网络获取,典型场景:Web Applet
从zip压缩包中读取,成为日后jar、war格式的基础
运行时计算生成,使用最多的是:动态代理技术
由其他文件生成,典型场景:JSP应用从专有数据库中提取.class文件,比较少见
从加密文件中获取,典型的防Class文件被反编译的保护措施
2.2链接阶段 (验证,准备,解析)
(1)验证 Verify
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
(2)准备(Prepare)
-
为类变量分配内存并设置该类变量的默认初始值,就是0值
比如:private static int a=1;
准备阶段就是0,在初始化的时候才能是1 -
注意: 这里不包括用final修饰的static变量,因为final在编译时就会分配,准备阶段会显示初始化。
-
实例变量不分配内存和初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。
(3)解析(Resolve)
- 将常量池内的符号引用转换为直接引用的过程
- 事实上**,解析操作往往会随着JVM在执行完初始化之后再执行。**
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针,相对偏移量或者一个间接定位到目标的句柄。
- 解析动作主要针对类或者接口、字段、类方法、接口方法、方法类型等。
2.3初始化
(1)初始化阶段就是执行类构造器方法< clinit>()过程,
(2)该方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
如果类中没有静态变量或者静态代码块,则不会出现< clinit>()方法
(3)< clinit>()构造器方法中指令按语句在源文件中出现的顺序执行
(4)< clinit>()不同于类的构造器,在虚拟机中类的构造器是< init>()
(5)如果类有父类,JVM会保证子类的< clinit>()执行前,父类的< clinit>()已经执行完毕。
(6)虚拟机必须保证一个类的< client>()方法在多线程下被同步加锁。
2.3代码配合理解
private static int num=1;
static {
num=2;
number=20;
}
private static int number=10;
public static void main(String[] args) {
System.out.println(ClassInitTest.num);
System.out.println(ClassInitTest.number);
}
输出结果
2
10
之前我们学习的时候是不是记过这样一天结论:(静态变量、静态初始化块)>(变量、初始化块)>构造器 ,其中静态变量和静态初始化块是谁的顺序在上谁先执行,今天深究一下具体是怎么回事。
我们通过变量number进行说明,首先准备(Prepare) 阶段,将变量放入内存中的number是0,然后再初始化的时候,先将number的值赋为20,然后再将number的值改为10;
3、类加载器介绍
JVM规范只有两类加载器,引导类加载器(BootStrap ClassLoader)和用户自定义类加载器(User-Defined ClassLoader),自定义类加载器就是派生于抽象类ClassLoader的类的加载器 这里我们介绍最常见的三个类加载器。
3.1 启动类加载器BootstrapClassLoader
- 这个类的加载是用C/C++实现的,嵌套在JVM内部
- 它用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),提供JVM自身需要的类
- 并不继承自java.lang.ClassLoader,没有父加载器。
- 加载扩展类和应用程序类加载器,并指定为他们的父类加载器(注意不是父子结构)
- 出于安全考虑, Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
3.2 扩展类加载器Extension Classloader
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
- 派生于ClassLoader类
- 父类加载器为启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
3.3系统类加载器AppClassLoader
- java语言编写,由sun.misc. Launcher$AppClassLoader实现
- 派生于ClassLoader类
- 父类加载器为扩展类加载器
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
- 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
上面的三个加载器不是什么继承关系,他们就是一个等级制度,启动类加载器等级最高,其次是扩展类加载器,系统类加载器
3.4代码:
public class ClassLoaderTest {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(systemClassLoader);
//获取其上层扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
// sun.misc.Launcher$ExtClassLoader@14ae5a5
System.out.println(extClassLoader);
//启动类加载器,引导类加载器 c、C++实现的
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
//null 表示的引导类加载器
System.out.println(bootStrapClassLoader);
// 对于用户自定义类来说
// 默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(classLoader);
// String类使用引导类加载器进行说明---》java的核心类库都是使用引导类加载器进行加载的
ClassLoader classLoader1 = String.class.getClassLoader();
// null 说明是引导类加载器
System.out.println(classLoader1);
}
}
结果: