原文:深度分析Java的ClassLoader机制(源码级别)java
先来看Java程序是怎么工做的
程序员
为了更好的理解类的加载机制,咱们来深刻研究一下ClassLoader和他的loadClass()方法。数组
public abstract class ClassLoader
ClassLoader类是一个抽象类,sun公司是这么解释这个类的:安全
/** * A class loader is an object that is responsible for loading classes. The * class <tt>ClassLoader</tt> is an abstract class. Given the <a * href="#name">binary name</a> of a class, a class loader should attempt to * locate or generate data that constitutes a definition for the class. A * typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system. **/
大体意思以下:less
class loader
是一个负责加载classes
的对象,ClassLoader
类是一个抽象类,须要给出类的二进制名称,class loader
尝试定位或者产生一个class
的数据,一个典型的策略是把二进制名字转换成文件名而后到文件系统中找到该文件。dom
接下来咱们看loadClass
方法的实现方式:jvm
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
仍是来看sun公司对该方法的解释:ide
/** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <p><ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * */
大体内容以下:函数
使用指定的二进制名称来加载类,这个方法的默认实现按照如下顺序查找类:
调用findLoadedClass(String)
方法检查这个类是否被加载过
使用父加载器调用loadClass(String)
方法,若是父加载器为Null
,类加载器装载虚拟机内置的加载器调用findClass(String)
方法装载类,
若是,按照以上的步骤成功的找到对应的类,而且该方法接收的resolve
参数的值为true
,那么就调用resolveClass(Class)
方法来处理类。
ClassLoader
的子类最好覆盖findClass(String)
而不是这个方法。
除非被重写,这个方法默认在整个装载过程当中都是同步的(线程安全的)。源码分析
接下来,咱们开始分析该方法。
protected Class<?> loadClass(String name, boolean resolve)
该方法的访问控制符是protected
,也就是说该方法同包内和派生类中可用,返回值类型Class
。
这里用到泛型。这里使用通配符?
做为泛型实参表示对象能够 接受任何类型(类类型)。由于该方法不知道要加载的类究竟是什么类,因此就用了通用的泛型。
String name
要查找的类的名字;boolean resolve
,一个标志,true
表示将调用resolveClass(c)
处理该类。
throws ClassNotFoundException
该方法会抛出找不到该类的异常,这是一个非运行时异常。
synchronized (getClassLoadingLock(name))
看到这行代码,咱们能知道的是,这是一个同步代码块,那么synchronized
的括号中放的应该是一个对象。咱们来看getClassLoadingLock(name)
方法的做用是什么:
protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }
以上是getClassLoadingLock(name)
方法的实现细节,咱们看到这里用到变量parallelLockMap
,根据这个变量的值进行不一样的操做,若是这个变量是Null
,那么直接返回this
,若是这个属性不为Null
,那么就新建一个对象,而后在调用一个putIfAbsent(className, newLock);
方法来给刚刚建立好的对象赋值,这个方法的做用咱们一会讲。那么这个parallelLockMap
变量又是哪来的那,咱们发现这个变量是ClassLoader
类的成员变量:
private final ConcurrentHashMap<String, Object> parallelLockMap;
这个变量的初始化工做在ClassLoader
的构造函数中:
private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } }
这里咱们能够看到构造函数根据一个属性ParallelLoaders
的Registered
状态的不一样来给parallelLockMap
赋值。 我去,隐藏的好深,好,咱们继续挖,看看这个ParallelLoaders
又是在哪赋值的呢?咱们发现,在ClassLoader
类中包含一个静态内部类private static class ParallelLoaders
,在ClassLoader
被加载的时候这个静态内部类就被初始化。这个静态内部类的代码我就不贴了,直接告诉你们什么意思,sun
公司是这么说的:Encapsulates the set of parallel capable loader types
,意识就是说:封装了并行的可装载的类型的集合。
上面这个说的是否是有点乱,那让咱们来整理一下:
首先,在ClassLoader类中有一个静态内部类ParallelLoaders,他会指定的类的并行能力,若是当前的加载器被定位为具备并行能力,那么他就给parallelLockMap定义,就是new一个
ConcurrentHashMap<>(),那么这个时候,咱们知道若是当前的加载器是具备并行能力的,那么parallelLockMap就不是Null,这个时候,咱们判断parallelLockMap是否是Null,若是他是null,说明该加载器没有注册并行能力,那么咱们没有必要给他一个加锁的对象,getClassLoadingLock方法直接返回this,就是当前的加载器的一个实例。若是这个parallelLockMap不是null,那就说明该加载器是有并行能力的,那么就可能有并行状况,那就须要返回一个锁对象。而后就是建立一个新的Object对象,调用parallelLockMap的putIfAbsent(className,
newLock)方法,这个方法的做用是:首先根据传进来的className,检查该名字是否已经关联了一个value值,若是已经关联过value值,那么直接把他关联的值返回,若是没有关联过值的话,那就把咱们传进来的Object对象做为value值,className做为Key值组成一个map返回。而后不管putIfAbsent方法的返回值是什么,都把它赋值给咱们刚刚生成的那个Object对象。
这个时候,咱们来简单说明一下getClassLoadingLock(String className)的做用,就是:
为类的加载操做返回一个锁对象。为了向后兼容,这个方法这样实现:若是当前的classloader对象注册了并行能力,方法返回一个与指定的名字className相关联的特定对象,不然,直接返回当前的ClassLoader对象。
Class c = findLoadedClass(name);
在这里,在加载类以前先调用findLoadedClass
方法检查该类是否已经被加载过,findLoadedClass
会返回一个Class
类型的对象,若是该类已经被加载过,那么就能够直接返回该对象(在返回以前会根据resolve的值来决定是否处理该对象,具体的怎么处理后面会讲)。 若是,该类没有被加载过,那么执行如下的加载过程,
try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader }
若是父加载器不为空,那么调用父加载器的loadClass
方法加载类,若是父加载器为空,那么调用虚拟机的加载器来加载类。
若是以上两个步骤都没有成功的加载到类,那么
c = findClass(name);
调用本身的findClass(name)
方法来加载类。
这个时候,咱们已经获得了加载以后的类,那么就根据resolve
的值决定是否调用resolveClass
方法。resolveClass
方法的做用是:
连接指定的类。这个方法给
Classloader
用来连接一个类,若是这个类已经被连接过了,那么这个方法只作一个简单的返回。不然,这个类将被按照Java™
规范中的Execution
描述进行连接。
至此,ClassLoader
类以及loadClass
方法的源码咱们已经分析完了。那么,结合源码的分析,咱们来总结一下:
java中的类大体分为三种:
一、系统类
二、扩展类
三、由程序员自定义的类BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,咱们经常使用内置库 java.xxx.* 都在里面,好比 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,咱们将它称之为「根加载器」。
ExtensionClassLoader 负责加载 JVM 扩展类,好比 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名一般以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有不少 jar 包。
AppClassLoader 才是直接面向咱们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。咱们本身编写的代码以及使用的第三方 jar 包一般都是由它来加载的。
类装载方式,有两种:
一、隐式装载, 程序在运行过程当中当碰到经过
new
等方式生成对象时,隐式调用类装载器加载对应的类到jvm
中。
二、显式装载, 经过class.forName()
等方法,显式加载须要的类
类加载的动态性体现:
一个应用程序老是由n多个类组成,Java程序启动时,并非一次把全部的类所有加载后再运行,它老是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,由于java最先就是为嵌入式系统而设计的,内存宝贵,这是一种能够理解的机制,而用到时再加载这也是java动态性的一种体现
java类装载器
Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并非一个,而是三个,层次结构以下:
为何要有三个类加载器,一方面是分工,各自负责各自的区块,另外一方面为了实现委托模型,下面会谈到该模型
类加载器之间是如何协调工做的
前面说了,java中有三个类加载器,问题就来了,碰到一个类须要加载时,它们之间是如何协调工做的,即java是如何区分一个类该由哪一个类加载器来完成呢。 在这里java采用了委托模型机制,这个机制简单来说,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,若是Parent 找不到,那么才由本身依照本身的搜索路径搜索类”
下面举一个例子来讲明,为了更好的理解,先弄清楚几行代码:
Public class Test{ Public static void main(String[] arg){ ClassLoader c = Test.class.getClassLoader(); //获取Test类的类加载器 System.out.println(c); ClassLoader c1 = c.getParent(); //获取c这个类加载器的父类加载器 System.out.println(c1); ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器 System.out.println(c2); } }
运行结果:
。。。AppClassLoader。。。 。。。ExtClassLoader。。。 Null
能够看出Test
是由AppClassLoader
加载器加载的,AppClassLoader
的Parent
加载器是 ExtClassLoader
,可是ExtClassLoader
的Parent
为null
是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader
是用C++
语言写的,依java
的观点来看,逻辑上并不存在Bootstrap Loader
的类实体,因此在java
程序代码里试图打印出其内容时,咱们就会看到输出为null
。
类装载器ClassLoader
(一个抽象类)描述一下JVM
加载class
文件的原理机制
类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java
中类装载器把一个类装入JVM
,通过如下步骤:
一、装载:查找和导入Class文件
二、连接:其中解析步骤是能够选择的
(a)检查:检查载入的class文件数据的正确性
(b)准备:给类的静态变量分配存储空间
(c)解析:将符号引用转成直接引用(在Java中, 一个Java类将会编译成一个class文件. 在编译时, Java类并不知道所引用的类的实际地址, 所以只能使用符号引用来代替. 好比org.simple.People类引用了org.simple.Language类, 在编译时People类并不知道Language类的实际内存地址, 所以只能使用符号org.simple.Language来表示Language类的地址.)
三、初始化:对静态变量,静态代码块执行初始化工做
类装载工做由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()
方法自动构造的。