超详细java中的ClassLoader详解_classloader 支持正则-程序员宅基地

技术标签: java  

作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog

转自:https://blog.csdn.net/briblue/article/details/54973413

前言

ClassLoader 翻译过来就是 类加载器,普通的Java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解 ClassLoader 的加载机制,也有利于我们编写出更高效的代码。

ClassLoader 的具体作用就是将 class文件 加载到 jvm虚拟机 中去,程序就可以正确运行了。但是,jvm 启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习 ClassLoader 这种加载机制。

备注:本文篇幅比较长,但内容简单,大家不要恐慌,安静地耐心翻阅就是

Class文件的认识

我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。如我们编写一个简单的程序 HelloWorld.java

如图:

然后,我们需要在命令行中进行java文件的编译:

javacHelloWorld.java

可以看到目录下生成了.class文件。我们再从命令行中执行命令:

javaHelloWorld

上面是基本代码示例,是所有入门JAVA语言时都学过的东西,这里重新拿出来是想让大家将焦点回到 class文件 上,class文件 是字节码格式文件,java虚拟机并不能直接识别我们平常编写的 .java源文件,所以需要javac这个命令转换成 .class文件。另外,如果用 C 或者 Python 编写的程序正确转换成 .class文件后,java虚拟机也是可以识别运行的。更多信息大家可以参考这篇:

http://blog.csdn.net/zhangjg_blog/article/details/21486985

了解了 .class文件后,我们再来思考下,我们平常在 Eclipse 中编写的 java程序 是如何运行的,也就是我们自己编写的各种类是如何被加载到 jvm(java虚拟机) 中去的。

你还记得Java环境变量吗

初学java的时候,最害怕的就是下载 JDK 后要配置环境变量了,关键是当时不理解,所以战战兢兢地照着书籍上或者是网络上的介绍进行操作。然后下次再弄的时候,又忘记了而且是必忘。当时,心里的想法很气愤的,想着是–这东西一点也不人性化,为什么非要自己配置环境变量呢?太不照顾菜鸟和新手了,很多菜鸟就是因为卡在环境变量的配置上,遭受了太多的挫败感。

因为我是在Windows下编程的,所以只讲Window平台上的环境变量,主要有3个:JAVA_HOMEPATHCLASSPATH

JAVA_HOME

指的是你JDK安装的位置,一般默认安装在C盘,如:

C:\ProgramFiles\Java\jdk1.8.0_91

PATH

将程序路径包含在 PATH 当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javacjava两个命令。一般的:

PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%PATH%;

也就是在原来的PATH路径上添加JDK目录下的bin目录jre目录的bin.

CLASSPATH

CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

一看就是指向jar包路径。需要注意的是前面的.;.代表当前目录。

环境变量的设置与查看

设置可以右击我的电脑,然后点击属性,再点击高级,然后点击环境变量,具体不明白的自行查阅文档。查看的话可以打开命令行窗口:

echo%JAVA_HOME%

echo%PATH%

echo%CLASSPATH%

好了,扯远了,知道了环境变量,特别是 CLASSPATH 时,我们进入今天的主题Classloader.

Java类加载流程

Java语言系统自带有三个类加载器:

Bootstrap ClassLoader最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib 下的 rt.jar、resources.jar、charsets.jar 和 class等。另外需要注意的是可以通过启动jvm时指定 -Xbootclasspath 和 路径 来改变 Bootstrap ClassLoader 的加载目录。比如 java -Xbootclasspath/a:path 被指定的文件追加到默认的 bootstrap 路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。

Extention ClassLoader扩展的类加载器,加载目录 %JRE_HOME%\lib\ext 目录下的jar包和class文件。还可以加载 -D java.ext.dirs 选项指定的目录。

Appclass Loader也称为 SystemAppClass 加载当前应用的classpath的所有类。

我们上面简单介绍了 3个ClassLoader。说明了它们加载的路径。并且还提到了-Xbootclasspath-D java.ext.dirs这两个虚拟机参数选项。

加载顺序

我们看到了系统的3个类加载器,但我们可能不知道具体哪个先行呢?我可以先告诉你答案

1. Bootstrap CLassloder

2. Extention ClassLoader

3. AppClassLoader

为了更好的理解,我们可以查看源码,sun.misc.Launcher

http://www.grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/misc/Launcher.java

它是一个 java虚拟机 的入口应用:

源码有精简,我们可以得到相关的信息。

1.Launcher 初始化了 ExtClassLoader 和 AppClassLoader。

2.Launcher 中并没有看见 BootstrapClassLoader,但通过 System.getProperty("sun.boot.class.path") 得到了字符串 bootClassPath,这个应该就是 BootstrapClassLoader 加载的jar包路径。

我们可以先代码测试一下sun.boot.class.path是什么内容。

System.out.println(System.getProperty("sun.boot.class.path"));

得到的结果是:

可以看到,这些全是JRE目录下的jar包或者是class文件。

ExtClassLoader源码

如果你有足够的好奇心,你应该会对它的源码感兴趣:

我们先前的内容有说过,可以指定-D java.ext.dirs参数来添加和改变 ExtClassLoader 的加载路径。这里我们通过可以编写测试代码:

System.out.println(System.getProperty("java.ext.dirs"));

结果如下:

C:\ProgramFiles\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext

AppClassLoader源码

可以看到 AppClassLoader 加载的就是java.class.path下的路径。我们同样打印它的值:

System.out.println(System.getProperty("java.class.path"));

结果:

D:\workspace\ClassLoaderDemo\bin

这个路径其实就是当前 java工程目录bin,里面存放的是编译生成的class文件。

好了,自此我们已经知道了 BootstrapClassLoader、ExtClassLoader、AppClassLoader 实际是查阅相应的环境属性 sun.boot.class.path、java.ext.dirs 和 java.class.path 来加载资源文件的。

接下来我们探讨它们的加载顺序,我们先用 Eclipse 建立一个java工程:

然后创建一个Test.java文件:

publicclassTest{}

然后,编写一个 ClassLoaderTest.java 文件:

我们获取到了 Test.class 文件的类加载器,然后打印出来。结果是:

ClassLoaderis:sun.misc.Launcher$AppClassLoader@73d16e93

也就是说明 Test.class文件 是由 AppClassLoader 加载的。

这个 Test类 是我们自己编写的,那么 int.class 或者是 String.class 的加载是由谁完成的呢?我们可以在代码中尝试:

运行一下,却报错了:

提示的是空指针,意思是 int.class 这类基础类没有类加载器加载?

当然不是!int.class 是由 Bootstrap ClassLoader 加载的。要想弄明白这些,我们首先得知道一个前提。

每个类加载器都有一个父加载器

每个类加载器都有一个父加载器,比如加载 Test.class 是由 AppClassLoader 完成,那么 AppClassLoader 也有一个父加载器,怎么样获取呢?很简单,通过 getParent 方法。比如代码可以这样编写:

运行结果如下:

这个说明,AppClassLoader 的父加载器是 ExtClassLoader。那么 ExtClassLoader 的父加载器又是谁呢?

运行结果:

又是一个空指针异常,这表明 ExtClassLoader 也没有父加载器。那么,为什么标题又是每一个加载器都有一个父加载器呢?这不矛盾吗?为了解释这一点,我们还需要看下面的一个基础前提。

父加载器不是父类

我们先前已经粘贴了 ExtClassLoader 和 AppClassLoader 的代码:

可以看见 ExtClassLoader 和 AppClassLoader 同样继承自 URLClassLoader,但上面一小节代码中,为什么调用 AppClassLoader的getParent() 代码会得到 ExtClassLoader 的实例呢?先从 URLClassLoader 说起,这个类又是什么?先上一张类的继承关系图:

URLClassLoader 的源码中并没有找到 getParent() 方法。这个方法在 ClassLoader.java 中:

我们可以看到 getParent() 实际上返回的就是一个 ClassLoader 对象 parent,parent 的赋值是在 ClassLoader 对象的构造方法中,它有两个情况:

1.由外部类创建 ClassLoader 时直接指定一个 ClassLoader 为 parent。

2.由 getSystemClassLoader() 方法生成,也就是在 sun.misc.Laucher 通过 getClassLoader() 获取,也就是 AppClassLoader。直白的说,一个 ClassLoader 创建时如果没有指定 parent,那么它的 parent 默认就是 AppClassLoader。

我们主要研究的是 ExtClassLoader 与 AppClassLoader 的 parent 的来源,正好它们与 Launcher类 有关,我们上面已经粘贴过 Launcher 的部分代码。

我们需要注意的是:

代码已经说明了问题 AppClassLoader 的 paren t是一个 ExtClassLoader 实例。

ExtClassLoader 并没有直接找到对 parent 的赋值。它调用了它的父类也就是 URLClassLoder 的构造方法并传递了3个参数。

对应的代码:

答案已经很明了了,ExtClassLoader 的 parent 为null

上面张贴这么多代码也是为了说明 AppClassLoader的parent 是 ExtClassLoader,ExtClassLoader 的 parent 是null。这符合我们之前编写的测试代码。

不过,细心的同学发现,还是有疑问的我们只看到 ExtClassLoader 和 AppClassLoader 的创建,那么 BootstrapClassLoader 呢?

还有,ExtClassLoader 的父加载器为 null,但是 Bootstrap CLassLoader 却可以当成它的父加载器这又是为何呢?

我们继续往下进行。

Bootstrap ClassLoader是由C++编写的

Bootstrap ClassLoader 是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM 启动时通过 Bootstrap类 加载器加载 rt.jar 等核心jar包中的 class文件,之前的int.class,String.class都是由它加载。

然后呢,我们前面已经分析了,JVM 初始化 sun.misc.Launcher 并创建 Extension ClassLoader 和 AppClassLoader实例。并将 ExtClassLoader 设置为 AppClassLoader 的父加载器。Bootstrap 没有父加载器,但是它却可以作用一个 ClassLoader 的父加载器。比如 ExtClassLoader。这也可以解释之前通过 ExtClassLoader 的 getParent方法 获取为null的现象。具体是什么原因,很快就知道答案了。

双亲委托

我们终于来到了这一步了。

一个类加载器查找 class 和 resource 时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到 Bootstrap ClassLoader,如果 Bootstrap classloader 找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托

整个流程可以如下图所示:

这张图是用时序图画出来的,不过画出来的结果我却自己都觉得不理想。

大家可以看到2根箭头蓝色的代表类加载器向上委托的方向,如果当前的类加载器没有查询到这个 class对象 已经加载就请求父加载器(不一定是父类)进行操作,然后以此类推。直到 Bootstrap ClassLoader。如果 Bootstrap ClassLoader 也没有加载过此class实例,那么它就会从它指定的路径中去查找,如果查找成功则返回,如果没有查找成功则交给子类加载器,也就是ExtClassLoader,这样类似操作直到终点,也就是我上图中的红色箭头示例。用序列描述一下:

1.一个 AppClassLoader 查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。

2.递归,重复第1部的操作。

3.如果 ExtClassLoader 也没有加载过,则由 Bootstrap ClassLoader 出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是 sun.mic.boot.class 下面的路径。找到就返回,没有找到,让子加载器自己去找。

4.Bootstrap ClassLoader 如果没有查找成功,则 ExtClassLoader 自己在 java.ext.dirs 路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。

5.ExtClassLoader 查找不成功,AppClassLoader 就自己查找,在 java.class.path 路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。

上面的序列,详细说明了双亲委托的加载流程。我们可以发现委托是从下向上,然后具体查找过程却是自上至下

我说过上面用时序图画的让自己不满意,现在用框图,最原始的方法再画一次:

上面已经详细介绍了加载过程,但具体为什么是这样加载,我们还需要了解几个个重要的方法 loadClass()、findLoadedClass()、findClass()、defineClass()。

重要方法

loadClass()

JDK文档中是这样写的,通过指定的全限定类名加载 class,它通过同名的 loadClass(String,boolean) 方法:

protectedClassloadClass(Stringname,booleanresolve) throwsClassNotFoundException

上面是方法原型,一般实现这个方法的步骤是

1.执行 findLoadedClass(String) 去检测这个class是不是已经加载过了。

2.执行父加载器的 loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是 Bootstrap ClassLoader。这也解释了 ExtClassLoader 的 parent 为 null,但仍然说 Bootstrap ClassLoader 是它的父加载器。

3.如果向上委托父加载器没有加载成功,则通过 findClass(String) 查找。

如果class在上面的步骤中找到了,参数 resolve 又是true的话,那么 loadClass() 又会调用 resolveClass(Class) 这个方法来生成最终的Class对象。 我们可以从源代码看出这个步骤:

代码解释了双亲委托。要注意的是如果要编写一个 classLoader 的子类,也就是自定义一个 classloader,建议覆盖 findClass()方法,而不要直接改写 loadClass()方法。另外:

前面说过 ExtClassLoader 的 parent 为 null,所以它向上委托时,系统会为它指定 Bootstrap ClassLoader。

自定义ClassLoader

不知道大家有没有发现,不管是 Bootstrap ClassLoader 还是 ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?

如果要这样做的话,需要我们自定义一个 classloader。

自定义步骤

1.编写一个类继承自 ClassLoader 抽象类。

2.复写它的 findClass() 方法。

3.在 findClass() 方法中调用 defineClass()

defineClass()

这个方法在编写自定义 classloader 的时候非常重要,它能将 class 二进制内容转换成 Class对象,如果不符合要求的会抛出各种异常。

注意点

一个 ClassLoader 创建时如果没有指定 parent,那么它的 parent 默认就是 AppClassLoader。

上面说的是,如果自定义一个 ClassLoader,默认的 parent 父加载器是 AppClassLoader,因为这样就能够保证它能访问系统内置加载器加载成功的class文件。

自定义ClassLoader示例之DiskClassLoader

假设我们需要一个自定义的classloader,默认加载路径为 D:\lib 下的jar包和资源。

我们写编写一个测试用的类文件,Test.java:

然后将它编译过年class文件Test.class放到D:\lib这个路径下。

我们编写DiskClassLoader的代码:

我们在 findClass() 方法中定义了查找class的方法,然后数据通过 defineClass() 生成了Class对象。

现在我们要编写测试代码。我们知道如果调用一个 Test对象 的 say方法,它会输出”Say Hello”这条字符串。但现在是我们把 Test.class 放置在应用工程所有的目录之外,我们需要加载它,然后执行它的方法。具体效果如何呢?我们编写的 DiskClassLoader 能不能顺利完成任务呢?我们拭目以待。

我们点击运行按钮,结果显示:

可以看到,Test类的say方法正确执行,也就是我们写的 DiskClassLoader 编写成功。

回首

讲了这么大的篇幅,自定义ClassLoader才姗姗来迟。 很多同学可能觉得前面有些啰嗦,但我按照自己的思路,我觉得还是有必要的。因为我是围绕一个关键字进行讲解的。

关键字是什么?关键字路径

从开篇的环境变量

到3个主要的JDK自带的类加载器

到自定义的ClassLoader

它们的关联部分就是路径,也就是要加载的class或者是资源的路径。

BootStrap ClassLoader、ExtClassLoader、AppClassLoader 都是加载指定路径下的jar包。如果我们要突破这种限制,实现自己某些特殊的需求,我们就得自定义ClassLoader,自已指定加载的路径,可以是磁盘、内存、网络或者其它。



作者:木木00
链接:https://www.jianshu.com/p/48aaf87e1e82
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ws6013480777777/article/details/86604245

智能推荐

ZYNQ的定时器们(三)TTC定时器到底能干啥?_zynq xttcps_setinterval-程序员宅基地

文章浏览阅读3.5k次。ZYNQ的定时器们(三)TTC定时器到底能干啥?本文转载自:https://zhuanlan.zhihu.com/p/31643799?from_voters_page=trueZYNQPS部分的最后一种定时器TTC在UG585中的描述只有6页(P244-249),SDK中的API函数有15个,宏定义太多了,就没数了。那么TTC能干啥?忙完这阵子后终于可以来跟各位说道说道了。TTC定时器直译过来就是三路定时器,而ZYNQ中的PS有两个TTC,每一个定时器有三路,一共是6路。从上面的框图可以看出TTC每一_zynq xttcps_setinterval

MATLAB代码:基于二阶锥规划的主动配电网动态重构研究-程序员宅基地

文章浏览阅读42次。MATLAB代码:基于二阶锥规划的主动配电网动态重构研究参考文档:《考虑动态网络重构的主动配电网优化运行策略》参考了重构部分公式《主动配电网最优潮流研究及其应用实例》参考了二阶锥松弛部分公式仿真平台:MATLAB YALMIP+CPLEX优势:代码注释详实,适合参考学习,全程有讲解!,全程有讲解!程序非常精品!主要内容:代码主要主要研究的配电网优化,具体为配电网中的动态重构问题,代码分为两个部分,第一部分1)主动配电网单时段重构问题,重构结果以0-1变量表示,结果清晰明了;2)主动配电网多时段动态

无涯教程-PHP - xml_parse_into_struct函数-程序员宅基地

文章浏览阅读378次,点赞4次,收藏6次。无涯教程网提供xml_parse_into_struct - 语法 int xml_parse_into_struct ( resource $parser , stri...PHP 中的 xml_parse_into_struct函数 - 无涯教程网。它用于将任何格式化的XML解析为数组结构。它用于指定要使用的XML解析器。它用于指定XML数据的目标数组。它用于指定要解析的XML数据。它用于指定索引数据的目标数组。成功时返回1,失败时返回0。

找不到wifi,提示适配器的驱动程序可能出现问题_xioami 802.11n usb wireless adapter 驱动错误-程序员宅基地

文章浏览阅读3.6k次,点赞6次,收藏12次。找不到wifi,提示适配器的驱动程序可能出现问题_xioami 802.11n usb wireless adapter 驱动错误

【人工智能简史】第三章 第一个AI研究的黄金时代-程序员宅基地

文章浏览阅读1.4w次。在 1950、1960 年代,早期 AI 研究者们开发了一系列实验项目,如西蒙和纽埃尔的逻辑理论机(Logic Theorist)、麦卡锡的 Lisp 语言和明斯基的微世界(Micro World)等。总的来说,从 20 世纪 40 年代末到 50 年代的 AI 理论形成和发展过程,为后续的 AI 研究和应用奠定了坚实的基础。在本章中,我们将分析第一个 AI 研究的黄金时代,探讨其重要突破和成就,以及此阶段对 AI 发展历程产生的长远影响。这些算法穿插在人工智能的各个领域,推动着技术的创新与突破。

centos7.3(1611版本)安装增强工具(VirtualBox)-程序员宅基地

文章浏览阅读298次。一、场景说明: 虚拟软件使用VirtualBox,虚机操作系统使用CentOs7.3, 最小化安装后在虚机里面安装增强工具。 二、安装方法: 首先要先安装图形界面不然..._vboxwindowsadditions-amd64.exe下载

随便推点

【python爬虫 系列 最终篇】16.利用多线程多进程爬取qq音乐全站所有信息和音乐_python爬取qq音乐-程序员宅基地

文章浏览阅读1.2w次,点赞11次,收藏45次。实战 爬取qq音乐1.项目详情歌手分区:(a-#)整个爬虫项目按功能分为爬虫规则和数据入库,分别对应文件 music.py 和 music_db.py。爬虫规则大致如下:在歌手列表(https://y.qq.com/portal/singer_list.html)中按照字母类别对歌手进行分类,遍历每个分类下的每位歌手页面,然后获取每位歌手页面下的全部歌曲信息。根据该设计方案列出遍历..._python爬取qq音乐

crontab每天8点_linux定时任务crontab设置每分钟、每小时、每天、每周、每月、每年定时执行...-程序员宅基地

文章浏览阅读4.4k次。之前写过一篇《crontab定时任务的一些写法整理》,可以作为本文的参考。今天对几种情况再写一下例子。crontab每分钟定时执行:*/1 * * * * service mysqld restart //每隔1分钟执行一次*/10 * * * * service mysqld restart //每隔10分钟执行一次crontab每小时定时执行:0 */1 * * * service mysql..._crontab每天19点和8点

图解系列--HTTP报文,HTTP状态码_http响应报文中状态码的字段是什么-程序员宅基地

文章浏览阅读823次,点赞23次,收藏19次。Http报文,Http状态码_http响应报文中状态码的字段是什么

利用java 快速实现加减乘数余运算_java 生产加减乘除取余题-程序员宅基地

文章浏览阅读941次。import java.util.Scanner;import java.math.*;public class Main { public static void main(String[] args) { Scanner sc=new Scanner(System.in); String s1 = sc.next(); String s2 = sc.next(); BigInte_java 生产加减乘除取余题

NS版暗黑破坏神3金手指开发教程(16)-程序员宅基地

文章浏览阅读3.2k次。上一节,我们学会了全幻化的制作,功力精进了一步,这一节,将会讲解全图纸的制作,也基本上是金手指教程的最后一节了,通过这一节,读者将会看到如何将逆向程序分析方法使用得淋漓尽致,面对任何困难也能无坚不摧1. 我们搜索图纸英文recipe,在sAllRecipes函数中发现了图纸类型一共有4种,分别是,铁匠,附魔工匠,珠宝匠,卡奈魔盒,也就是0,1,2,3,这个很重要,一会儿会用到2. 在U...

Revit导出IFC文件-程序员宅基地

文章浏览阅读1.9w次,点赞2次,收藏9次。Revit 导出IFC文件对于了解过BIM的人员来说,IFC文件应该并不陌生!那么我们常用的revit软件在绘制完建筑信息模型之后,如何有效的导出相应的完整的IFC文件呢?是不是有人遇到过这种情况,直接用revit软件中导出功能,所得到的IFC文件再被打开时发现,看不到模型。对于这种情况,如何解决呢?本文便讲解如何解决这个问题!第一:找到exportlayers-ifc-..._revit导出ifc

推荐文章

热门文章

相关标签