你想写类加载器?或者你遇到了ClassCastException异常,或者你遇到了奇怪的LinkageError状态约束异常。应该仔细看看java类的加载处理了。java
一个Java类是由java.lang.ClassLoader类的一个实例加载的。因为java.lang.ClassLoader本身自己是一个抽象类因此一个类加载器只可以是java.lang.ClassLoader类的具体子类的实例。若是是这种状况,那么哪个类加载器来加载java.lang.ClassLoader这个类?(经典的"谁将会加载加载者"引导的问题)。事实证实JVM有一个内置的引导类加载器。引导加载器加载java.lang.ClassLoader和许多其余java平台类。sql
要加载一个具体的java类,例如com.acme.Foo,JVM调用java.lang.ClassLoader类的loadClass方法(事实上,JVM查找loadClassInternal方法-若是发现loadClassInternal方法则用loadClassInternal方法,不然JVM使用loadClass方法,而loadClassInternal方法会调用loadClass方法)。loadClass方法接收类名来加载类返回表示加载的类的java.lang.Class实例。事实上loadClass方法找到.class文件(或者URL)的实际字节,并调用defineClass方法来构造出java.lang.Class类的字节数组。加载器上调用loadClass方法的加载器称之为初始化加载器(即,JVM启动加载使用这个加载器).可是,启动加载器不是直接加载类的-而是可能委托给另一个类加载器(例如,它的父加载器)-它本身也可能委派给另一个加载器去加载等等。最终在委托链中的某些类加载器对象调用defineClass方法加载有关的类(com.acme.Foo)。
这个特殊的类加载器叫作com.acme.Foo的确切加载器。在运行时,一个java类是由类的彻底限定类名和和类加载器肯定其惟一性的。若是指定相同的类名(即,相同的彻底限定类名)的类是由两个不一样的类加载器加载的,那么这些类是不一样的-即便这些.class的字节码是相同的而且都是从相同的位置进行加载的(相同的URL)。数组
即使是一个简单的"hell world"java程序,也有至少3种类加载器。缓存
引导类加载器oracle
让咱们假设你正在运行一个"hello world" java程序。咱们来看一下类的加载流程。JVM用应用类加载器加载主方法(main)所在的类。若是你运行下面的程序spa
class Main { public static void main(String[] args) { System.out.println(Main.class.getClassLoader()); javax.swing.JFrame f = new javax.swing.JFrame(); f.setVisible(true); SomeAppClass s = new SomeAppClass(); }
它会打印以下内容
sun.misc.Launcher$AppClassLoader@17943a4code
每当一些其它的类引用在Main类中被解析时,JVM用Main所在类的明确的加载器-应用类加载器-作为初始化加载器。在上面的列子中,为了加载javax.swing.JFrame类JVM将使用应用类加载器作为一个初始化加载器。即,JVM将用应用类应用作为初始化加载器。即。JVM将调用loadClass()方法(loadClassInternal方法)在应用类加载器中。应用类加载器委托给扩展类加载器。
扩展加载器检查这是不是一个启动类(用私有方法 - ClassLoader.findBootstrapClass),启动类加载器是否从rt.jar加载过它。
当SomeAppClass的引用类被解析时,JVM有着相同的过程-用应用类加载器作为初始化加载器。
应用加载器委托给扩展加载器,扩展加载器检查启动加载器,启动加载器找不到"SomeAppClass"类,
因而扩展加载器检查"SomeAppClass"类是否在扩展jars里,结果发现不在。
因而应用类加载器检查在应用的CLASSPATH下的.class字节,若是找到了则进行加载,若是没有找到,将会抛出NoClassDefFoundError异常。对象
Class是由具体的类加载器与类的彻底限定类名惟必定义的。blog
若是具体的类加载器不一样,即便.class字符是从文件系统中的相同位置进行加载的Classes也是不一样的。get
类加载器委托给父加载器进行加载。
加载Bar类中引用的Foo类,JVM使用Bar类的确切的类加载器作为初始化加载器。JVM会在Bar类的确切加载器上会调用loadClass()方法加载Foo类。
JVM缓存->运行时的类每次初始化加载都将被记录。JVM将会缓存用于之后的解析。即,loadClass()方法不会对于每一次引用都调用。这能确保时间的不变性-即,一个类加载器不容许加载相同类名但字节码不一样的类。
他是由缓存来实现的。好的类加载器应该经过调用ClassLoader得call()方法来检查缓存。
Understanding Java class loading https://blogs.oracle.com/sundararajan/entry/understanding_java_class_loading