超详细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

智能推荐

Hive中partition by和distribute by区别_partition by distribute by-程序员宅基地

文章浏览阅读1.2k次,点赞3次,收藏4次。通常查询时会对整个数据库查询,而这带来了大量的开销,因此引入了partition的概念,在建表的时候通过设置partition的字段, 会根据该字段对数据分区存放,更具体的说是存放在不同的文件夹,这样通过指定设置Partition的字段条件查询时可以减少大量的开销。1)partition by [key..] order by [key..]只能在窗口函数中使用,而distribute by [key...] sort by [key...]在窗口函数和select中都可以使用。_partition by distribute by

游标(cursor )是什么?_c# cursor-程序员宅基地

文章浏览阅读7.3k次。Private SQL Area A private SQL area holds information about a parsed SQLstatement and other session-specific information for processing. When a serverprocess executes SQL or PL/SQL code, the process_c# cursor

listview使用的一些心得_listview的使用——购物商城实验心得-程序员宅基地

文章浏览阅读616次。近日在用ListView中的一些注意点,和公用代码,整理如下1.ListView.Items.Clear而不是ListView.Clear一般如果ListView是动态填充的,我们在填充之前都会先进行清理。但需要注意一下,我们是清理Items,如果去直接Clear整个ListView,就连原先定义好的列都没有了2.给ListView绑定数据ListView并不能直接_listview的使用——购物商城实验心得

【数据结构】哈希表——C语言_c语言哈希表-程序员宅基地

文章浏览阅读6.1k次,点赞19次,收藏74次。主要介绍C语言提供的哈希函数。_c语言哈希表

性能分析之两个性能瓶颈分析案例_客户性能分析案例-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏8次。最近处理了几个项目中的性能问题,来跟大家唠唠。这几个问题是非常常见的。性能瓶颈就有这么个特点,大部分瓶颈分析到最后,都给人有一种猛拍大腿突然醒悟的感觉。但是在分析到具体的原因之前,都是抓耳挠腮,百思不解。这就是性能瓶颈的魅力所在了。问题一:单队列网卡导致软中断高这个问题在专栏也好,公众号文章也好,都不止一次描述过。但是看到过的同学们似乎还是没办法在项目中非常快速地定位出来。问题的现象我就不描述了,无非就是 TPS 压不上去。先看一下这个压力的路径。这是一个清晰的路径。我们直接来说判断的关_客户性能分析案例

Python大数据基础之数据清洗(数据转换篇)_数据清洗 数据转换-程序员宅基地

文章浏览阅读6.7k次。数据转换是指将数据转换或统一成适合于挖掘的形式。数据规范化大致分为三种最大最小规范化、z-score规范化、按小数定标规范化。一、z-score规范化z-score规范化:又称标准差规范化或零均值规范化,数据处理后服从标准正态分布,也是比较常用的规范化方法。其中为对应特征的均值,为标准差。python中有两种方法实现:利用Pandas中DataFrame的apply函数;利用sklearn库已经封装好的方法。1.apply()函数DataFrame.apply(func,axis=_数据清洗 数据转换

随便推点

【语音识别】MFCC+VQ说话人识别系统【含GUI Matlab源码 1153期】-程序员宅基地

文章浏览阅读24次。MFCC+VQ说话人识别系统完整的代码,方可运行;可提供运行操作视频!适合小白!

GPS数据处理——mooc《零基础学Java语言》-(浙大翁凯)第六周编程题(2)-程序员宅基地

文章浏览阅读351次。题目内容:NMEA-0183协议是为了在不同的GPS(全球定位系统)导航设备中建立统一的BTCM(海事无线电技术委员会)标准,由美国国家海洋电子协会(NMEA-The National Marine Electronics Associa-tion)制定的一套通讯协议。GPS接收机根据NMEA-0183协议的标准规范,将位置、速度等信息通过串口传送到PC机、PDA等设备。N..._7-1 gps数据处理分数 15全屏浏览题目切换布局作者 翁恺单位 浙江大学nmea-0183协

C#Winform DataGridView问题:列的 FillWeight 值总和不能超过 65535_fillweight值总和不能超过65535-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏2次。列的 FillWeight 值总和不能超过 65535这里写自定义目录标题写程序添加列时,超过655列会报错,而我的列为900多列。看了下报错,查了下fillweight是相对于其他列的显示宽度,初始值为100,所以655列就达到了上限。csdn上的建议基本是换控件或者循环绑定列,但我是绑定datatable,循环会很麻烦,在stackflow上搜索了下得到了一个很好的解决方案,记录一下://绑定datagridview事件columnAdded。private void dataGridViewN_fillweight值总和不能超过65535

jsp运行原理-程序员宅基地

文章浏览阅读164次。下面看看JSP文件在各个阶段的内容。源文件:success.jsp<%@ page contentType="text/html;charset=gb2312"%><html><head><title>登录成功</title></head><body>&_每一个jsp在执行时会先转化为( )。

SQL分离附加扩展收缩与四种类型文件_收缩 分离-程序员宅基地

文章浏览阅读5.6k次。SQL Server四种类型文件 .mdf为主数据文件,包含数据库启动信息,指向数据库其他文件。.ndf为次要数据文件,次要数据文件是可选的,由用户定义并存储用户数据。次要文件可用于将数据分散到多个磁盘上。另外,如果数据库超过了单个 Windows 文件的最大大小,可以使用次要数据文件,这样数据库就能继续增长。.ldf为事务日志文件,用于记录所有事务以及每个事务所做的数据库修改。事..._收缩 分离

SQL关键字详解-程序员宅基地

文章浏览阅读5k次,点赞2次,收藏26次。SQL关键字详解_sql关键字

推荐文章

热门文章

相关标签