首页 > 其他 > 详细

类加载器介绍

时间:2020-04-11 14:26:41      阅读:110      评论:0      收藏:0      [点我收藏+]

一、jvm类加载器

类加载器是实现了"通过一个类的全限定名来描述此类的二进制字节流"这个动作的代码模块,简单的说就是将字节码class文件读取后加载到jvm内存中的类

从jvm虚拟机的角度来讲,只存在两种不同的类加载器:

一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用c++实现,属于虚拟机的一部分,并没有实现java.lang.classLoader类

另一种是其他的类加载器,都由java语言实现,独立于虚拟机外部,并且全部继承自java.lang.classLoader类,java系统提供了2个实现:Extension ClassLoader和Application ClassLoader

分别介绍一下这3个类加载器:

启动类加载器(Bootstrap ClassLoader):负责将存放在JAVA_HOME/lib目录中的,或者被-Xbootclasspath参数特意指定的路径中的几个特定名称的jar包类库(比如rt.jar)加载到jvm内存中,这个启动器无法被java程序直接引用(只有native本地方法可以调用它加载类),如果用户在自定义类加载器时想委托它来加载类,可以直接使用null代替(后面会说为什么)

扩展类加载器(Extension ClassLoader):这个类的实现在sun.mise.Launcher里面,它负责加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量特意指定的路径中的所有类库

应用程序类加载器(Application ClassLoader):这个类的实现也在sun.mise.Launcher里面,它也是ClassLoader中getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器,它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用,如果应用程序没有自己定义过类加载器,这个就是程序中默认的了

 

二、双亲委派机制

每个类加载器都拥有一个独立的类名称空间,所以如果有2个不同的类加载器,即使它们加载了同1个class文件,得到的2个类都是不同的

这种特性会产生一个坏处,比如用户自己另外写了一个java.lang.Object类用某个类加载器加载了,系统中将会出现2个Object类,应用程序的表现将会出现混乱,为了保证java程序的稳定,jvm的类加载器之间存在一种层次关系,叫做双亲委派机制(当然用java.lang.Object类只是举个例子,jvm中会判断如果类路径以"java"开头就不予加载并报错,这也是一种保护)

技术分享图片

通过这张有点包浆的图可以看到其实jvm的类加载器是有父子关系的,类加载器的基类ClassLoader中有一个parent属性:

技术分享图片

每个继承了ClassLoader的类加载器的parent属性就是它的父级,比如Application ClassLoader的parent属性是Extension ClassLoader,Extension ClassLoader的parent属性是null(为什么Bootstrap ClassLoader就是null了呢,因为它是c++实现的,java中没法直接引用)

为什么要有这层父子关系呢?核心就在基类ClassLoader的loadClass方法里:

技术分享图片

方法的含义是当被调用时,先看一下自身类加载器有没有加载过这个类,如果加载过就返回,如果没加载过,不会自己加载,是先调用父类的loadClass方法,让父类去加载

到了父类,同理,如果没加载过,就去找爷爷类,而不是自己加载,直到parent属性为null,表示到顶了爸爸是Bootstrap ClassLoader了,就调用findBootstrapClassOrNull这个native本地方法使用启动类加载器加载类试一试

如果这个类是"java.lang.Object"这种放在JAVA_HOME/lib/rt.jar中的系统类的话,爷爷类就加载成功了,一路返回,如果这个类就是自己用户路径ClassPath下的,那爷爷类加载失败,再回到父类,如果还没有再回到当前类,然后才会往下走进入c==null的情况,调用findClass()方法自己去加载类

再看一下书本中对双亲委派模型的描述就非常清晰了:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当附加再起反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

这是一种使用组合关系来复用父加载器的逻辑,双亲委派的好处是:

任何一个类加载器要加载java.lang.Object类的话,都会最终委派给处于模型最顶端的启动类加载器进行加载,而不是每个加载器自己加载一个Object造成混乱

既然Application ClassLoader和Extension ClassLoader的实现都在sun.mise.Launcher里面,那最后我们来看看Launcher的代码:

技术分享图片

看到Launcher的无参构造函数中,创建了Extension ClassLoader和Application ClassLoader这对父子.. 它们的实现类是静态的,在Launcher中作为静态内部类,都继承自URLClassLoader,这里不展开,着重看一下创建方法的入参

创建Extension ClassLoader的getExtClassLoader()方法是没有入参的,进去可以看到:

技术分享图片

一路看到最终调用了父类super的构造方法,第二个参数类加载器ClassLoader这里传了null,在最终的父类ClassLoader中看到:

技术分享图片

所以parent=null了,就表示Extension ClassLoader的爸爸是无法被引用的Bootstrap ClassLoader,再回过去看一眼前面的loadClass方法瞬间就明白了,同理如果你写自定义类加载器,父级想委托给Bootstrap ClassLoader的话,传null即可

再看创建Application ClassLoader的getAppClassLoader(var1)方法,传入了var1参数,这个var1就是刚才创建的Extension ClassLoader,代码调进去看super构造方法第2个参数就是传的这个var1,这就是认爸爸了..

 

三、破坏双亲委派模型

双亲委派模型是在jdk1.2才被引入的,设计者推荐给开发者的一种类加载器实现方式,不是强制的,因为loadClass是可被重写的,在实际情况中也存在破坏这个模型的使用方式

在jdk1.0版本时ClassLoader和它的loadClass方法就已经存在了,当时的自定义类加载器都是继承ClassLoader然后重写loadClass的,这些都不符合,后来jdk1.2才在ClassLoader中增加了findClass方法,不提倡用户去重写loadClass方法,而是改为重写findClass方法来符合双亲委派机制(loadClass中会先去父级找,再调用findClass)

除了遗留代码,有这些非常著名打破双亲委派的实现场景:SPI、OSGi、Java Web服务器,OSGi是java模块化的规范,它避免不了模块间类加载的问题,有自己的类加载逻辑,不是很了解就不说了,说一下另外2个:

1、jdbc与类加载器

 

 

2、tomcat的类加载器

 

3、spring容器的类加载

类加载器介绍

原文:https://www.cnblogs.com/ctxsdhy/p/12679370.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!