Java ClassLoader
Posted shiyuan310
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java ClassLoader相关的知识,希望对你有一定的参考价值。
以前确实没关注过这个知识点,现在复习的时候,好多文章都提到了类加载过程。并且在讲解反射知识点的时候肯定会提到ClassLoader。今天总结回顾下:
JAVA类装载方式,有两种:
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。
2.显式装载, 通过class.forname()等方法,显式加载需要的类
类加载的动态性体现:
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现
java类装载器
JDK 默认提供了如下几种ClassLoader
-
Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib
,-Xbootclasspath
参数指定的路径以及%JAVA_HOME%/jre/classes
中的类。 - ExtClassLoader
Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp loader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载
%JAVA_HOME%/jre/lib/ext
,此路径下的所有classes目录以及java.ext.dirs
系统变量指定的路径中类库。 - AppClassLoader
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个
getSystemClassLoader
方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
综上所述,它们之间的关系可以通过下图形象的描述:
为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。
类加载器之间是如何协调工作的
前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”
下面举一个例子来说明,为了更好的理解,先弄清楚几行代码:
1 package lesson1214; 2 3 public class TestClassLoader { 4 5 public static void main(String[] args) { 6 7 ClassLoader c1 = TestClassLoader.class.getClassLoader(); 8 9 System.out.println(c1); 10 11 ClassLoader c2 = c1.getParent(); 12 13 System.out.println(c2); 14 15 ClassLoader c3 = c2.getParent(); 16 17 System.out.println(c3); 18 } 19 20 }
运行结果:
sun.misc.Launcher$AppClassLoader@43794494
sun.misc.Launcher$ExtClassLoader@4e857327
null
可以看出Test是由AppClassLoader加载器加载的,AppClassLoader的Parent
加载器是 ExtClassLoader,但是ExtClassLoader
的Parent
为 null
是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java
程序代码里试图打印出其内容时,我们就会看到输出为null
。int.class是由Bootstrap ClassLoader加载的
类装载器ClassLoader(一个抽象类)描述一下JVM加载class文件的原理机制
类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤:
1、装载:查找和导入Class文件
2、链接:其中解析步骤是可以选择的 (a)检查:检查载入的class文件数据的正确性
(b)准备:给类的静态变量分配存储空间
(c)解析:将符号引用转成直接引用
3、初始化:对静态变量,静态代码块执行初始化工作
类装载工作由ClassLoder
和其子类负责。JVM在运行时会产生三个ClassLoader:根装载器,ExtClassLoader
(扩展类装载器)和AppClassLoader
,其中根装载器不是ClassLoader的子类,由C++编写,因此在java中看不到他,负责装载JRE的核心类库,如JRE目录下的rt.jar,charsets.jar等。ExtClassLoader
是ClassLoder
的子类,负责装载JRE扩展目录ext下的jar类包;AppClassLoader
负责装载classpath路径下的类包,这三个类装载器存在父子层级关系****,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下使用AppClassLoader装载应用程序的类。
Java装载类使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoder
装载一个类时,除非显示的使用另外一个ClassLoder
,该类所依赖及引用的类也由这个ClassLoder
载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全方面考虑的,试想如果一个人写了一个恶意的基础类(如java.lang.String
)并加载到JVM
将会引起严重的后果,但有了全盘负责制,java.lang.String
永远是由根装载器来装载,避免以上情况发生 除了JVM默认的三个ClassLoder
以外,第三方可以编写自己的类装载器,以实现一些特殊的需求。类文件被装载解析后,在JVM
中都有一个对应的java.lang.Class
对象,提供了类结构信息的描述。数组,枚举及基本数据类型,甚至void
都拥有对应的Class
对象。Class
类没有public
的构造方法,Class
对象是在装载类时由JVM
通过调用类装载器中的defineClass()
方法自动构造的。
Class文件的认识
我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。我们都需要将其进行编译,生成.class文件才可以在JVM上运行。class文件是字节码格式文件,java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。
了解了.class文件后,我们再来思考下,我们平常在Eclipse中编写的java程序是如何运行的,也就是我们自己编写的各种类是如何被加载到jvm(java虚拟机)中去的。
因为我是在Windows下编程的,所以只讲Window平台上的环境变量,主要有3个:JAVA_HOME、PATH、CLASSPATH。
JAVA_HOME:指的是你JDK安装的位置,一般默认安装在C盘,如
C:\\Program Files\\Java\\jdk1.8.0_911
PATH:将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javac和java两个命令。
PATH=%JAVA_HOME%\\bin;%JAVA_HOME%\\jre\\bin;%PATH%;也就是在原来的PATH路径上添加JDK目录下的bin目录和jre目录的bin.
CLASSPATH, 这个ClassLoader的时候会用到
CLASSPATH=.;%JAVA_HOME%\\lib;%JAVA_HOME%\\lib\\tools.jar
父加载器不是父类
我们先前已经粘贴了ExtClassLoader和AppClassLoader的代码。
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
可以看见ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但上面一小节代码中,为什么调用AppClassLoader的getParent()代码会得到ExtClassLoader的实例呢?先从URLClassLoader说起,这个类又是什么?
先上一张类的继承关系图
详细code分析可以看帖子:https://blog.csdn.net/briblue/article/details/54973413
上面张贴这么多代码也是为了说明AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。这符合我们之前编写的测试代码。
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找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。
用序列描述一下:
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路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
上面的序列,详细说明了双亲委托的加载流程。我们可以发现委托是从下向上,然后具体查找过程却是自上至下。
关于里面介绍的自定义ClassLoader没有关注,后面回给补上。
转载帖子:
http://www.cnblogs.com/doit8791/p/5820037.html
https://blog.csdn.net/briblue/article/details/54973413
以上是关于Java ClassLoader的主要内容,如果未能解决你的问题,请参考以下文章