本文我们讲述类加载子系统的功能以及作用
一、类加载子系统
类加载子系统的位置图
1.1类加载子系统的作用
- 类加载子系统负责从文件系统或网络中加载class文件,class文件再开头有特定的文件标识
- ClassLoader(类加载器)只负责class文件的加载,至于他是否可与运行,则由ExecutionEngine(执行引擎)决定
- 加载类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
- class文件存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出一个一摸一样的实例。文件记载到JVM中,被称为DNA元数据模板,放在方法区。
- 在.class文件到JVM再到最终称为元数据模板,此过程就要一个运输工具(类加载器Class Loader),扮演了一个快递员的角色。
1.2类的加载过程
加载:
1.通过一个类的全限定名称获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.class对象,最为方法去这个类的各个数据的访问入口
链接:
1.验证(Verify):
所有的java字节码文件都是以CAFE BABE开头的,这个叫做“魔数”,用于区分java的字节码文件和其他类型的文件或者判断这个字节码文件是否受损(因为一般情况下,如果文件受损,开头的误码率最高)
- 目的是在于确保class文件的字节流中包含信息符合当前虚拟机要求,确保文件的正确性,不会危害到虚拟机本身
- 主要包裹四种验证,文件格式验证(文件开头字节码)、元数据验证、字节码验证、符号引用验证
2.解析(Rosolve)
- 将常量池内的符号引用转换为直接引用的过程
- 解析操作往往会伴随着JVM在执行完初始化之后在执行
- 符号引用就是一组符号来描述引用的目标,符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接执行目标的指针、相对偏移量或一个间接定位到目标的句柄
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应的常量池
3.初始化(init)
- 初始化阶段就是执行类构造器方法clinit()的过程。
- 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
- 构造方法中指令按语句在源文件中出现的顺序执行。
- clinit()不同于类的构造器。(构造器是虚拟机较小的init())
- 若该类拥有父类,JVM会保证子类的clinit()执行前,父类的clinit()已经执行完毕
- 虚拟机必须保证一个类的clinit()方法在多线程下被同步加锁
1.3类加载的分类**
- JVM支持两种类型的类加载器,分别为引导类加载器和自定义加载器
- java虚拟机是将所有派生于抽象类ClassLoader的类加载器都划分于自定义加载器
常见类加载器:
Bootstrap Class Loader:由C和C++编写,其他都是java代码编写
获取Java类加载器:
package demo1;
/**
* @author :mmzs
* @date :Created in 2021/12/31 17:42
* @description:
* @modified By:
* @version: $
*/
public class verify {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层,扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1b6d3586
//获取其上层:获取不到引导类加载器
ClassLoader bootStrapClassLoader = parent.getParent();
System.out.println(bootStrapClassLoader);//null
//用户自定义类加载器来说是谁,默认使用系统类加载器来进行加载
ClassLoader classLoader = verify.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类是使用引导类加载器进行加载的,核心类库都是引导类加载的
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
-
启动类加载器(也叫引导类加载器,BootStrap ClassLoader)
1.该类加载器使用C/C++语言实现的,嵌套在JVM内部(所以上方获取时为Null)
2.它用来加载java核心类库((JAVA_HOME/jre/lib/rt.jar、resources.java或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
3.并不继承ClassLoader,没有父类加载器(老大)
4.加载扩展类加载器和应用程序类加载器,都由引导类加载器加载
5.处于安全考虑,启动类加载器只加载包名为java、javax、sun开头的类 -
扩展类加载器(虚拟机自带的ExtClassLoader)
1.java语言来编写,由sun.misc.Launcher$ExtClassLoader来实现
2.派生于ClassLoader类
3.父类加载器为启动类加载器
4.如果用户Jar包放在此目录下(jre\lib\ext子目录),也会由扩展类加载器所加载 -
系统类加载器(AppClassLoader)
1.java语言编写,sun.misc.Launcher$AppClassLoader实现
2.派生于ClassLoader类
3.父类加载器为扩展类加载器
4.她负责classpath或系统属性,java.class.path指定路经下的类库
5.该类加载是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载 -
自定义加载器
自定义类加载器只有在特定场景下才需要定义,情况如下:
1.隔离加载器
2.修改类的加载方式
3.扩展加载源
4.防止源码泄露
实现步骤:
1.继承抽象类ClassLoader类的方式,实现自己的加载类,满足要求
2.jdk 1.2之前是重写loadClass()方法,之后不建议重写loadClass方法,充血findClass方法
3.如果没有复杂的需求可以直接结成URLClassLoader类
双亲委派机制***:
java虚拟机对class文件采取的是按需加载的方式,也就是说当需要使用该类的时候才会对它的class文件加载到内存生成class对象。而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式,即把请求交给父类处理他是一种任务委派模式。
工作原理:
- 如果一个类加载器收到了类的加载请求,他不会自己先去加载,而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类,则进一步的向上委托,依次递归,请求达到顶层的启动类加载器
- 如果父类可以完成加载任务,则返回成功,倘若父类加载器无法完成加载任务,子类加载器才会自行加载,这就是双亲委派机制。
如果自行创建一个java.lang.string的话,也是按照java jdk的实例,而不是自己创建的包下的
双亲委派的优势:
- 避免类的加载重复
- 保护程序的安全,防止核心API篡改
沙箱安全机制:
自定义String类,看但是在加载自定义String类的时候会首先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带文件,这可以保证对java核心源代码的保护,这就是沙箱安全机制。
学习产出:
JVM相关的学习会让你更加了解Java核心的东西,所以学起来吧兄弟们
该文章内容时哔哩哔哩的尚硅谷JVM课程的讲述大家可以边看边学习我也会实时更新进行总结