JVM:类加载机制之类加载器
JVM设计者把类加载阶段中的“通过'类全名'来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
1.类与类加载器
对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。
2.双亲委派模型
先找后询问,存在就不用向上询问
从虚拟机的角度来说,只存在两种不同的类加载器:
一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于JVM自身的一部分。
一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽java.lang.ClassLoader。
从Java开发人员的角度来看,大部分Java程序一般会使用到以下三种系统提供的类加载器:
1)启动类加载器(BootstrapClassLoader):负责加载JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。
2)扩展类加载器(ExtensionClassLoader):该加载器主要是负责加载JAVA_HOME\lib\,该加载器可以被开发者直接使用。
3)应用程序类加载器(ApplicationClassLoader):该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。(开发者写的类默认都是应用程序类加载器加载的)
我们的应用程序都是由这三类加载器互相配合进行加载的,我们也可以加入自己定义的类加载器。这些类加载器之间的关系如下图所示:
如上图所示的类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。
该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,再自顶向下尝试加载,如果都没有加载到,那么这个类加载器才会尝试自己去加载。
因此请求询问是自底向上!类加载操作是自顶向下!
1. 在rt.jar包中的java.lang.ClassLoader类中,我们可以查看类加载实现过程的代码,具体源码如下:protected synchronized Class loadClass(String name, boolean resolve)
2. throws ClassNotFoundException {
3. // 首先检查该name指定的class是否有被加载
4. Class c = findLoadedClass(name);
5. if (c == null) {
6. try {
7. if (parent != null) {
8. // 如果parent不为null,则调用parent的loadClass进行加载
9. c = parent.loadClass(name, false);
10. } else {
11. // parent为null,则调用BootstrapClassLoader进行加载
12. c = findBootstrapClass0(name);
13. }
14. } catch (ClassNotFoundException e) {
15. // 如果仍然无法加载成功,则调用自身的findClass进行加载
16. c = findClass(name);
17. }
18. }
19. if (resolve) {
20. resolveClass(c);
21. }
22. return c;
23. }
通过上面代码可以看出,双亲委派模型是通过loadClass()方法来实现的,根据代码以及代码中的注释可以很清楚地了解整个过程其实非常简单:先检查是否已经被加载过,如果没有则调用父加载器的loadClass()方法,如果父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载器加载失败,则先抛出ClassNotFoundException,然后再调用自己的findClass()方法进行加载。
3.自定义类加载器
若要实现自定义类加载器,只需要继承java.lang.ClassLoader 类,并且重写其findClass()方法即可。java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等,ClassLoader 中与加载类相关的方法如下:
方法 |
说明 |
getParent() |
返回该类加载器的父类加载器。 |
loadClass(String name) |
加载名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findClass(String name) |
查找名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findLoadedClass(String name) |
查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。 |
defineClass(String name, byte[] b, int off, int len) |
把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。 |
resolveClass(Class<?> c) |
链接指定的 Java 类。 |
注意:在JDK1.2之前,类加载尚未引入双亲委派模式,因此实现自定义类加载器时常常重写loadClass方法,提供双亲委派逻辑,从JDK1.2之后,双亲委派模式已经被引入到类加载体系中,自定义类加载器时不需要在自己写双亲委派的逻辑,因此不鼓励重写loadClass方法,而推荐重写findClass方法。
双亲委派模式优势
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer
,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge
类(该类是胡编的)呢?该类并不存在java.lang
中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang
是核心API包,需要访问权限,强制加载将会报出如下异常
几点思考
0.顶层的ClassLoder无法加载底层ClassLoder的类!!!
解决方案:
源码
Java虚拟机的第一个类加载器是Bootstrap,这个加载器很特殊,它不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。
这也是我们在测试时为什么发现
System.class.getClassLoader()
结果为null的原因,这并不表示System这个类没有类加载器,而是它的加载器比较特殊,是BootstrapClassLoader
,由于它不是Java类,因此获得它的引用肯定返回null。委托机制具体含义
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?- 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。 - 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
- 还可以直接调用
ClassLoader.loadClass()
方法来指定某个类加载器去加载某个类。
- 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
委托机制的意义 — 防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:- 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
- 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
破坏双亲委派模型?基于反射
假设写两个一摸一样的类(类名相同),一个放置在classpath下,一个放置在任意目录下但是用-Xbootclasspath参数与启动类加载器进行绑定!毫无疑问,当执行main函数的时候,一定会执行任意目录下的那个类!这是双亲委派模型的实现!
但是能否强制执行classpath下的那个类呢?答案是可以的。
通过反射机制将classpath下的那个类提前加载到classpath下,那么当执行main函数的时候,由于AppClassLoder已经加载了这个类,既然已经找到了就不必向上询问了,也就可以直接执行classpath下的类了!
实例:
上面抛出异常是由于:我们重载修改了classLoder方法使得使用自定义的类加载器进行加载,但是我们知道在任何类进行加载之前都需要将父类加载进JVM!(jvm类加载过程中有谈到),但是呢,由于我这个类没有继承任何类,因此他的父类就是Object类,而Object类存在于rt.jar中,由BootClassLoader加载,因此由于找不到Object类所以抛出异常!!!因此Object类最后由父类加载器进行加载。而demoA是由自定义的OrderClassLoader进行加载的!!!!
热替换
JVM:类加载机制之类加载器相关推荐
- jvm类加载机制和类加载器_在JVM之下–类加载器
jvm类加载机制和类加载器 在许多开发人员中,类加载器是Java语言的底层,并且经常被忽略. 在ZeroTurnaround上 ,我们的开发人员必须生活,呼吸,饮食,喝酒,并且几乎与类加载器保持亲密关 ...
- JVM的私房笔记(一)类加载机制与类加载器 by 葵鱼
前言 准备写这份笔记的时候,想法还是较为简单的,就是希望能将学到的,听到的,查到的,看到的东西做一个总结,以免后面自己遗忘.同时我将会以段落和副标题的形式编写.有什么问题或者错误的地方,还请大家多指正 ...
- java赋值语句_深度分析:面试阿里,字节99%会被问到Java类加载机制和类加载器...
1. 类加载机制 所谓类加载机制就是JVM虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成虚拟机可以直接使用的Jav类型,即Java.lang.Class. 2. 类加载的过 ...
- 深入理解Java虚拟机——JVM类加载机制(类加载过程和类加载器)
一.什么是类加载机制? 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的时机 类 ...
- JVM:类加载机制之类加载过程
类加载机制概念 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.准备.解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制.* Class文件由 ...
- java中类加载机制、类加载过程和类加载器层次
1.类加载机制 jvm把class文件加载到内存,并对数据进行校验.解析和初始化,最终形成jvm可以直接使用的java类型的过程. (1)加载 将class文件字节码内容加载到内存中,并将这些静态数据 ...
- jvm类加载机制_JVM 类加载机制
学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑 Java 的类加载机制,这是 Java 的一个很重要的创新点,曾经也是 Java 流行的重要原因之一. Oracle 当初引入这个机制是为 ...
- java类加载机制、类加载器、自定义类加载器
类加载机制 java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化( ...
- 29.类加载机制、类加载过程、加载、验证、准备、解析、初始化、总结
29.类加载机制 29.1.类加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载 ...
最新文章
- 设计模式C++实现(6)——建造者模式
- C# 的 Console类
- ARC132D-Between Two Binary Strings【贪心】
- jsencrypt vue使用_在Vue项目中使用jsencrypt.js对数据进行加密传输
- 广州市岑村教练场考科目二,惊险通过,经验总结
- Python菜鸟入门:day03运算符
- 线段树入门 (zz)
- mysql one database_MYSQL学习笔记one
- linux面试题线程与进程,​一道面试题:说说进程和线程的区别
- 鹏拍:软件行业上市十大关注问题
- 精品Uniapp的餐厅餐馆饮订餐点餐管理系统实现的App
- 生命旅程中何生命个体
- Unity SRP初识之URP
- 30人围成一圈的小游戏。c语言
- Google退出Android有影响吗?
- 易语言模拟器中控源码 全新手游模拟器通用中控源码, 适用于各种游戏, 源码现成的只需要更换游戏就可以用哦
- MySQL - binlog 图文详解
- 离线安装ruby、rubygems
- 岭南(含广东广西海南)地形及DEM下载
- gprMax中任意不规则形状建模与模拟
热门文章
- 安装和配置SQL Server 2016 With SP1
- 使用Goldengate 实现Oracle for Oracle 单向数据同步
- simulink积分模块和微分模块区别
- 数字图像处理实验(10):PROJECT 05-01 [Multiple Uses],Noise Generators
- 全云端万能小程序_万能门店全云端独立版微信小程序源码V4.0.10,全五端源码下载...
- 从对话框中传递参数到视图类
- CentOS 7.6 MySQL 8.0 RPM包方式安装及新特性介绍
- 如何在 Azure 中的 Linux 经典虚拟机上设置终结点
- 20155225 实验三《敏捷开发与XP实践》实验报告
- windows环境下运行.sh文件