深入理解Java虚拟机(类加载器)

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

类加载器

类加载器有什么作用呢?

对于任意的一个类,都必须由加载它的类加载器,这个类本身一起共同确立其在JVM中的唯一性

  • 每一个类加载器,都拥有一个独立的类名称空间

换种说话就是:比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义

  • 否则,即使这两个类来源于同一个Class文件,被同一个JVM加载
    • 只要加载它们的类加载器不同,这两个类也不相等

【注】:这里所指的相等,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果

  • 也包括了使用instanceof关键字做对象所属关系判定等各种情况

JVM中有几类类加载器呢?

其实从JVM的角度看,大致可以分为两类:

启动类加载器(Bootstrap ClassLoader):

  • JVM自身的一部分,由C++实现的
    • 只针对HotSpot虚拟机,JDK9以后也采用了类似的虚拟机与Java类互相配合来实现Bootstrap ClassLoader的方式
      • 所以HotSpot也有一个无法获取实例的代表Bootstrap ClassLoader的Java类存在

其他所有的类加载器:

  • 全部由Java实现,独立存在于VM外部,并全都继承自抽象类java.lang.ClassLoader

【注】:从Java开发人员的角度来看,类加载器分的更细致

  • 自JDK1.2以来,Java一直保持着三层类加载器,双亲委派的类加载器结构

类加载器双亲委派模型(Parents Delegation Model)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

启动类加载器(Bootstrap ClassLoader):

也叫引导类加载器,负责加载(存放在<JAVA_HOME>\lib目录,或者被Xbootclasspath参数所指定的路径中存放的

  • 而且是JVM能够识别(按文件名识别,如rt.jar、tools.jar))的类库加载到JVM的内存中

启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时

  • 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可

扩展类加载器(Extension Class Loader):

  • 在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的

它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库

通过名字我们可以推断出,JVM开发团队允许用户将自己的类库放置在<JAVA_HOME>\lib\ext目录中

  • 以扩展Java SE的功能。因为是由Java语言实现的,所以我们可以直接在程序中使用该类加载器加载Class文件

【注】:在JDK9以后其实扩展机制已经可以被模块化代替了,因为模块化有天然的扩展能力

应用程序类加载器(Application Class Loader):

sun.misc.Launcher$AppClassLoader来实现

  • 该类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以我们也称其为系统类加载器

它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器

【注】:如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

自定义类加载器(User Class Loader):

JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的

当然我们也可以加入自定义的类加载器进扩展,比如:

  • 增加存储于磁盘之外的Class文件来源;

  • 实现类的隔离、重载等功能;

双亲委派模型的要求是什么?

顶层必须是启动类加载器

其余的类加载器都有自己的父类加载器;注意它们之间不是继承关系,而是通常使用组合(Composition)关系来复用父加载器的代码

双亲委派模型"的工作过程:

如果一个类加载器收到了类加载的请求,它首先会把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此

因此所有的加载请求最终都应该传送到最顶层的启动类加载器中

  • 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

双亲委派模型的好处:

Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系

例如类java.lang.Object,它存放在rt.jar之中

无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类

反之如果没有该模型,那么不同的类加载器可能会加载同一个名为java.lang.Object的类

那么 在程序的classPath上就会有不同的Object类,那么我就不能保障Java类型体系了

双亲委派模型的核心代码: (全部集中在java.lang.ClassLoader的loadClass()方法之中)

protected synchronized Class<?> loadClass(String name, boolean resolve) throwsClassNotFoundException
{
	// 首先,检查请求的类是否已经被加载过了
	Class c = findLoadedClass(name);
	if (c == null) {  //如果未加载过就交给父类加载器
		try {
			if (parent != null) {
			c = parent.loadClass(name, false);
			} else {
				c = findBootstrapClassOrNull(name);
			}
		} catch (ClassNotFoundException e) {
		// 如果父类加载器抛出ClassNotFoundException,说明父类加载器无法完成加载请求
		}
		if (c == null) {
			// 在父类加载器无法加载时,再调用本身的findClass方法来进行类加载
			c = findClass(name);
		}
	}
	if (resolve) {
		resolveClass(c);
	}
	return c;
}

逻辑解析:

  • 检查请求加载的类是否已经被加载过

  • 如果没有则调用父加载器的loadClass()方法

  • 如果父加载器为空,则默认使用启动类加载器为父加载器

  • 如果父加载器失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法尝试进行加载

破坏双亲委派模型模型(一共三次):

第一次破坏:

因为JDK1.2以后双亲委派模型才被引入

但是在这之前类加载器的概念以及java.lang.ClassLoader就已经存在

  • 所以面对当时已经存在的自定义类加载器,我们为了兼容它们,无法避免ClassLoader被子类覆盖的可能性

因此我们想到一个解决方法,就是在java.lang.ClassLoader中添加一个新的protected方法findClass()

  • 并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码

(在双亲委派模型的核心代码中的第④步,这样就做到了妥协,如果父加载器加载失败,则会调用自己的findClass()方法

  • 既按照用户意愿去加载类, 又保证符合双亲委派模型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二次破坏:

由自身缺陷导致,我们回想一下该模型的好处,无论哪一个类加载器要加载一个类,最终都是委派给处于模型最顶端的启动类加载器进行加载

如果该类加载加载失败,则抛出异常转而让下一层加载器加载(越基础的类由越上层的加载器进行加载)),这样解决了基础类型的不一致问题

但是有一个问题:

  • 如果基础类型(被启动类加载器加载的)要调用用户的代码,那怎么办?

针对这个问题,我们引入了线程上下文类加载器(Thread Context ClassLoader)

该加载器可以通过java.lang.Thread类的setContext-ClassLoader()方法进行设置

如果在创建线程的时候还没有创建,我们就通过它的父线程继承一个

  • 如果其父线程甚至整个程序都没有设锅置,那我们就将应用类加载器充当线程上下文类加载器

而这种行为是父类加载器请求子类加载器去完成类加载,也就是变相的打破了双亲委派模型的层次结构逆向使用类加载器

  • 已经违背了双亲委派模型的一般性原则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三次破坏:

用户对程序动态性的追求而导致的。动态性指的是一些很热的名词,比如代码热替换(Hot Swap)、模块热部署(Hot Deployment)等

比如IBM公司的OSGi,实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器

当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换

在OSGi环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构

它的查找顺序只有在开头符合双亲委派模型的原则,其余的类查找都是在平级的类加载器中进行

OSGI在运行期动态热部署上的优势是JDK9以后Sun/Oracle公司所提出Jigsaw不能的

  • 其只能局限于静态地解决模块间封装隔离和访问控制的问题
  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值