理解类加载器

标签: 理解 加载器 | 发表时间:2016-05-01 10:22 | 作者:HEL_WOR
出处:http://blog.csdn.net

转载请注明: http://blog.csdn.net/HEL_WOR/article/details/51287786

5个月前写了第一篇博客就是类加载器,这两天在common-pool2中的驱逐逻辑里遇到了对上下文类加载器的使用,觉得需要重写写一次

类记载器通俗来理解就是用来加载class文件,当需要创建一个对象时,都会使用到类加载器,对于继承自ClassLoader抽象类的子类加载器实现加载功能都是调用defineClass来完成的,这点在URLClassLoader中重写的findClass方法中可以看见。

平时我们在编程中能直接接触到的类加载器有系统类加载器,当前类加载器,线程上下文加载器。
1.对于一个我们创建的一个控制台程序,对于main方法所在类是用哪个类加载器来完成的?
其是由AppClassLoader完成的,可以debug进源码跟着就能发现。

2.为什么会使用AppClassLoader类加载器来进行加载工作,不是还有扩展类加载器,启动类加载器么?
对于双亲委派模型,其限制了每个类加载器可能加载的class文件路径,也规定了父子关系和加载顺序。对于我们的main方法所在类,当其通过编译产生class文件,是被放在类路径上的,而这个地址只能由AppClassLoader类加载器发现,对于扩展类加载器,引导类加载器,它们是看不到这个路径的。

在源码里:
AppClassLoader的加载路径:
这里写图片描述
ExtClassLoader的加载路径:
这里写图片描述
BootstrapClassLoader加载路径:
这里写图片描述

其中AppClassLoader,ExtClassLoader是URLClassLoader的内部类继承自ClassLoader,BootstrapClassLoader是JVM内嵌的,由C++实现。
光看ClassLoader这一段双亲委派的源码,读上去似乎比较好理解。
这里写图片描述
即:

1.加锁。
2.判断是否已被加载.
3.否,则交给父类加载
4.父类加载失败,交给启动类加载
5.启动类加载失败,交给子类加载

看上去比较简单的步骤,那么,问几个问题:

1.即然在 catch (ClassNotFoundException e) {}这段没有做任何操作,为什么还需要这段try..catch..,仅仅是为了吃掉异常?
2.交给子类去加载,这段逻辑是如何描述的?
3.是否看出了递归?

我们debug源码来解释上面提到递归的原因:
步骤一:初始使用AppClassLoader调用其重写的loadClass方法进行加载:
除非之前的AppClassLoader搜索路径已被保存且能从之中找到需要被加载的Class文件,否则AppClassLoader委托其父类ExtClassLoader加载。
这里写图片描述
这里写图片描述

步骤二:对于ClassLoader类中的loadClass方法,只有在URLClassLoader的内部类AppClassLoader中进行了重写,因此对于其父类ExtClassLoader,调用器loadClass方法,其实是在调用ClassLoader类的loadClass方法,即自己调用了自己。
这里写图片描述
可以看见,当前类加载器仍是AppClassLoader。其在委托其父类ExtClassLoader进行加载。

步骤三:

    c = parent.loadClass(name, false);

此时parent是ExtClassLoader,继续调用其父类返回BootstrapClassLoader,由于BootstrapClassLoader是内嵌于JVM底层用C++描述的,因此这个用null表示,在其后的一步代码:

  c = findBootstrapClassOrNull(name);

当判断得知parent为null后,会执行这步代码,即交给BootstrapClassLoader加载。这也是我们在一些书里看到的如果想把类委托给BootstrapClassLoader加载,则直接对指定类加载器为null即可。
这里写图片描述

启动程序后,对类的加载,都是从AppClassLoader开始的。
原因是,在代码编译链接完成后,首次加载时会调用getSystemClassLoader方法,其返回AppClassLoader。
这里写图片描述

上面描述得是子类到父类的委托过程 ,下面描述当父类无法加载时由子类自行加载的过程。
其实现过程用到了上面已进入的递归,try…catch…,findClass方法。
还是用这份代码:
这里写图片描述

try..catch…是为ExtClassLoader和AppClassLoader切换而准备的。
此时目标class文件已交由引导类加载器加载,当前加载失败后,由当前类加载器ExtClassLoader继续调用findClass方法。
这里写图片描述

ucp描述的是ExtClassLoader的查找路径,当在路径里没有找到目标class文件,会抛出ClassNotFoundException异常,随后此方法被递归栈的下一层捕获,当前方法出栈,类加载器被切换为AppClassLoader。

这里写图片描述

可以看到此时类加载器已变为了AppClassLoader。
此时再调用findClass方法,需要查找的路径改变:

这里写图片描述

以上就是父类加载失败由子类加载的过程。
我们来测试一下类加载的过程,按照上面的代码逻辑,AppClassLoader会先委托ExtClassLoader进行加载。那么我们将需要查找的地址当前根目录下,但将目标Aop文件放在java.lib.ext中,按照双亲委派原则,目标class文件的类加载器应该是ExtClassLoader而不是AppClassLoader,即使我们指定在类路径下查找。
这里写图片描述

Main类在当前根目录下而不会在java.lib.ext下,因此其会被AppClassLoader找到并加载。

但是,上面的加载逻辑会出现一个问题。
对于一些核心接口,我们需要用到第三方的类实现,但是根据双亲委派的原则,类的加载只能向上委托,核心接口位于核心类中,由启动类加载器加载,而第三方实现类放置于类路径上或者扩展路径上,我们需要向下委派AppClassLoader或者ExtClassLoader去其路径上搜索目标class文件并加载。
这就线程上下文类加载器出现的原因。线程上下文类加载器如果不设置,会默认使用AppClassLoader作为加载器。当我们需要逆向的去加载类,
我们设置设置当前线程的上下文类加载器,由当前线程去加载目标类。

作者:HEL_WOR 发表于2016/5/1 2:22:35 原文链接
阅读:256 评论:0 查看评论

相关 [理解 加载器] 推荐:

理解类加载器

- - CSDN博客推荐文章
转载请注明: http://blog.csdn.net/HEL_WOR/article/details/51287786. 5个月前写了第一篇博客就是类加载器,这两天在common-pool2中的驱逐逻辑里遇到了对上下文类加载器的使用,觉得需要重写写一次. 类记载器通俗来理解就是用来加载class文件,当需要创建一个对象时,都会使用到类加载器,对于继承自ClassLoader抽象类的子类加载器实现加载功能都是调用defineClass来完成的,这点在URLClassLoader中重写的findClass方法中可以看见.

javaSE之类加载器

- - ITeye博客
类加载器,说白了就是加载类的呵呵.   .类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象.   .当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:.                  BootStrap----------JRE/lib.jar      根节点类加载器.

关于类加载器内存泄露的分析

- - ITeye博客
从上个世纪90年代Java诞生之日起,Java的类和资源的加载就一直是个问题. 由于它增加了启动和初始化时间,因此这个问题在Java应用服务器上则尤为明显. 为了缓解这个问题,大家试过了不同的访问,比如说以exploaded方式部署,但这只对简单的应用有效;还有2001年发明的Java热插拔的机制.

Java类加载器classLoader的工作机制

- - CSDN博客推荐文章
类加载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件. 在Java中,类转载器把一个类装入JVM中,需要经过以下步骤:. 1.装载:查找和导入Class文件. 2.链接: 执行校验、准备和解析步骤,其中解析步骤是可以选择的:. a)校验: 检查载入Class文件数据的正确性;. b)准备:给类的静态变量分配存储空间;.

泛型理解

- - ITeye博客
泛型不是协变的,例如下面的代码不是正确的. 之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束. 例如,下面代码中的ifThenElse()方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:. 注意,您可以调用ifThenElse(),而不用显式地告诉编译器,您想要T的什么值.

理解inode

- - haohtml's blog
inode是一个重要概念,是理解Unix/Linux文件系统和硬盘储存的基础. 我觉得,理解inode,不仅有助于提高系统操作水平,还有助于体会Unix设计哲学,即如何把底层的复杂性抽象成一个简单概念,从而大大简化用户接口. 下面就是我的inode学习笔记,尽量保持简单. 理解inode,要从文件储存说起.

理解OAuth 2.0

- - 阮一峰的网络日志
OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版. 本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为 RFC 6749. 为了理解OAuth的适用场合,让我举一个假设的例子. 有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来.

理解云计算

- 车东 - oneoo's 私家花园
  现在互联网最热门的关键字“云计算”,大大小小的公司纷纷加入到这块领域. 简单来说,目前的“云计算”主要分为:SaaS、PaaS和IaaS三大类.   其中SaaS云计算,为软件即服务的概念. 把传统客户端软件部署在互联网上,用户只需要一个浏览器就可以使用到软件的模式. 其实早在2000年就已经有B/S结构的软件服务,与现在所说的SaaS云计算相近,但此前的B/S结构软件服务,数据库等服务端是需要用户自行部署的,而非由软件提供商进行统一部署.

深入理解JVM

- 小伟 - ITeye论坛最新讨论
1   Java技术与Java虚拟机. 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API). 图1   Java四个方面的关系. 运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件).