JVM架构图
JVM自带的类加载器
1.启动类加载器(引导类加载器,Boot Strap Class Loader)
- 这个类是使用C/C++语言实现的,嵌套在JVM内部,它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的内容)。
- 它并不继承java.lang.ClassLoader,没有父类加载器;加载扩展类和应用程序类加载器并指定为他们的父类加载器。
- 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。
- 启动类加载范围为jre/lib目录下的resources.jar、rt.jar、sunrsasign.jar、jsse.jar、jce.jar、charsets.jar、jfr.jar以及jre目录下的classes。
2.扩展类加载器(Extension Class Loader)
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,派生于ClassLoader类。
- 它的父类加载器为启动类加载器,从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/lib/ext子目录(扩展目录)下加载类库。
- 如果用户将自己创建的JAR放在此目录下,也会自动由扩展类加载器加载。
3.应用程序类加载器(系统类加载器,Application Class Loader)
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,派生于ClassLoader类,它的父类加载器为扩展类加载器。
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库,该类是程序中默认的类加载器,一般Java应用的类都是由它来完成加载;可以通过classLoader#getSystemClassLoader()方法来获取该类加载器。
类加载过程
1.加载(类加载子系统中的Loading)
将类的class文件读入内存,并为创建一个java.lang.Class对象。也就是说,当程序使用任何类时,系统都会为它新建一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供。开发者可以通过继承ClassLoader基类来创建自己的类加载器。
JVM规范允许系统预先加载某些类。
2.链接(类加载子系统中的Linking)
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
(1)验证
验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。这是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
(2)准备
类准备阶段负责为类的静态变量分配内存,并设置默认初始值。
(3)解析
将类的二进制数据中的符号引用替换成直接引用。
3.初始化(类加载子系统中的Initialization)
初始化是为类的静态变量赋予正确的初始值。
准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
类加载时机
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName("com.lyj.load"))
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类
类加载机制
1.全盘负责机制
所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
2.双亲委派机制
所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
步骤:
A.类加载器受到类加载的请求。
B.将这个请求向上委托给父类加载器完成,一直向上委托,直到启动类加载器。
C.启动加载器检测是否能够加载这个类,能加载就结束,使用当前的加载器;否则,通知子加载器进行加载。
D.重复步骤C。
3.缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
引用资料
https://www.cnblogs.com/xxeleanor/p/13579361.html
https://blog.csdn.net/justloveyou_/article/details/72217806
https://www.bilibili.com/video/BV1iJ411d7jS?p=3
https://blog.csdn.net/m0_38075425/article/details/81627349