Java类加载器

1.类加载概述

Java类加载器:是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存中

虚拟机类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型

2.类加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。其中验证、准备、解析三个部分统称为连接

在这里插入图片描述
第一步:加载

“加载”是虚拟机类加载的第一个阶段,在这个阶段中,虚拟机要完成3件事情:

1.通过权限定名,获取类的二进制字节流

2.将字节流的静态数据结构转化为方法区的运行时数据结构

3.在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区中这个类数据结构的访问入口

第二步:验证

“验证”是连接阶段的第一步,这一阶段是为了确保 Class 文件内的字节流格式符合Java虚拟机要求,不会危害虚拟机安全

验证总体上分为4个阶段: 文件格式验证、元数据验证、字节码验证、符号引用验证

第三步:准备

“准备”阶段是正式为类变量(仅仅是类变量,即 static 修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行

第四步:解析

“解析“阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能简介定位到的目标的句柄。直接引用是和虚拟机实现的内存布局有关的。如果有了直接引用,那引用的目标必定已经在内存中存在了

第五步:初始化

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正的开始执行类中定义的java程序代码了(或者说是字节码)

参考文章:

1.https://www.cnblogs.com/htyj/p/11673053.html

2.https://www.cnblogs.com/linghu-java/p/8551360.html

3.https://www.cnblogs.com/fsmly/p/10394972.html

4.https://www.cnblogs.com/cheng21553516/p/11261647.html

5.https://www.cnblogs.com/luohanguo/p/9469851.html

3.类加载器

类的加载是由类加载器完成的

类加载器有两种:

(1)Java虚拟机自带的类加载器,分别为启动类加载器,扩展类加载器和系统类加载器

(2)用户自定义的类加载器,是Java.lang.ClassLoader的子例实例

1.启动类加载器(Bootstrap)

启动类加载器是最底层的类加载器,是虚拟机的一部分,它是由C++语言实现的,且没有父加载器,也没有继承Java.lang.ClassLoader类。

它主要负责加载由系统属性“sun.boot.path”指定的路径下的核心类库(即<JAVA_HOME>\jre\lib),出于安全考虑,启动类加载器只加载java,javax,sun开头的类

2.扩展类加载器(Extension)

扩展类加载器是指由原Sun公司实现的sun.misc.Launcher$ExtClassLoader类(每个版本的JDK不一样),它是由Java语言编写,父加载器是启动类加载器。

负责加载<JAVA_HOME>\jre\lib\ext目录下的类库或者系统变量"java.ext.dirs"指定的目录下的类库

3.系统类加载器(System)

系统类加载器也称之为应用类加载器,是纯Java类,是原SUN公司实现的sun.misc.Launcher$AppClassLoader类(每个版本的JDK不一样)

它的父类加载器是扩展类加载器

负责从classpath环境变量或者系统属性java.class.path所指定的目录中加载类

它是用户自定义的类加载器的默认父加载类

一般情况下,改类加载器是程序中默认的类加载器,可以通过ClassLoader.getSystemClassLoader()方法直接获取

4.类加载器小结

在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,同时我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它一种任务委派模式

4.类加载器的双亲委派机制

双亲委派机制工作原理:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成(坑爹模式)

双亲委派机制优势:

(1)可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次

(2)考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Object,而直接返回已加载过的Object.class,这样便可以防止核心API库被随意篡改

5.ClassLoader

所有的类加载器(除启动类加载器)都必须继承Java.lang.ClassLoader。它是一个抽象类,主要的方法如下:

1.loadClass()方法

在ClassLoader的源码中,有一个方法loadClass(String name,boolean resolve),这里就是双亲委托模式的代码实现

从源代码中可以观察到它的执行顺序

注意:只有父类加载器加载不到类时,才会调用findClass方法进行类的查找,所以,在定义自己的类加载器时,不要覆盖掉该方法,而应该覆盖掉findClass方法

2.findClass()方法

在自定义类加载器时,一般需要覆盖这个方法,且ClassLoader中给出了一个默认的错误实现

3.defineClass()方法

protected final class<?> defineclass(String name,byte[] b,int off,int len) throws classFormatError{}

用来将byte字节解析成虚拟机能够识别的Class对象

defineClass()方法通常与findClass()方法一起使用

在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法获取要加载类的字节码,然后调用defineClass()方法生成Class对象

4.resolveClass()方法

连接指定的类。类加载器可以使用此方法来连接类

6.URLClassLoader

在Java.net包中,JDK提供了一个更加易用的类加载器URLClassLoader,它扩展了ClassLoader,能够从本地或者网络上指定的位置加载类

我们可以使用该类作为自定义的类加载器使用

构造方法:

(1)public URLClassLoader(URL[] urls):指定要加载的类所在的URL地址,父类加载器默认为系统类加载器

(2)public URLClassLoader(URL[] urls,ClassLoader parent):指定要加载的类所在的URL地址,并指定父类加载器

案例1:加载磁盘上的类

案例2:加载网络上的类

7.自定义加载器

实现自定义类加载器的步骤:

1.继承ClassLoader

2.重写findClass()方法

案例1:自定义文件类加载器

案例2:自定义网络类加载器

案例3:热部署类加载器

当我们调用loadClass()方法加载类时,会采用双亲委派模式,即如果类已经被加载,就从缓存中获取,不会重新加载

如果同一个class被同一个类加载器多次加载,则会报错。因此,我们要实现热部署让同一个class文件被不同的类加载器重复加载即可

但是不能调用loadClass方法,而应该调用findClass()方法,避开双亲委托模式,从而实现同一个类被多次加载,实现热部署

案例4:线程上下文类加载器

在Java中存在很多的服务提供者接口SPI,是Java提供的一套用来被第三方实现或者扩展的API,这些接口一般由第三方提供实现,常见的SPI由JDBC,JNDI等

这些SPI的接口(比如JDBC中的Java.sql.Driver)属于核心类库,一般存在rt.jar包中,由启动类加载器加载

而第三方实现的代码一般作为依赖jar包存在classpath路径下,由于SPI接口中的代码需要加载具体的第三方实现类并调用其相关方法,SPI的接口类是由启动类加载器加载的,启动类加载器无法直接加载位于classpath下的具体实现类

由于双亲委派模式的存在,启动类加载器也无法反向委托AppClassLoader加载SPI的具体实现类。在这种情况下,Java提供了线程上下文类加载器用于解决以上问题

线程上下文类加载器可以通过Java.lang.Thread的getContextClassLoader()来获取,或者通过setContextClassLoader(ClassLoader cl)来设置线程的上下文类加载器。

如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类或资源

显然这种加载类的方式破坏了双亲委派模型,但它使得Java类加载器变得更加灵活

8.类的显式与隐式加载

类的加载方式:是指虚拟机将class文件加载到内存的方式

显式加载:是指在Java代码中通过调用ClassLoader加载class对象

比如Class.forName(String name);或者是this.getClass().getClassLoader().loadClass()加载类

隐式加载:指不需要在Java代码中明确调用加载的代码,而是通过虚拟机自动加载到内存中

比如在加载某个class时,该class引用了另外一个类的对象,那么这个对象的字节码文件就会被虚拟机自动加载到内存中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值