1.类与类加载器
对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。(比较两个类是否'相等',只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源一个同一个class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等)
这里所指的‘相等’,包括类的Class对象的equals()方法,isAssignableFrom()方法,isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判断等情况。
import java.io.IOException; import java.io.InputStream; public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is = getClass().getResourceAsStream(fileName); if(is==null){ return super.loadClass(name); } byte[] b=new byte[is.available()]; is.read(b); return defineClass(name,b,0,b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; Object obj =myLoader.loadClass("ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof ClassLoaderTest); } }
这个类加载器加载名为ClassLoaderTest的类并实例化了这个类的对象。从两行输出结果可以看到,第一行输出确定是类ClassLoaderTest实例化的对象,但是第二句可以发现这个对象与类ClassLoaderTest做的属性检查却返回了false,这是因为虚拟机中存在了两个ClassLoaderTest,一个是系统应用程序类加载器加载的,一个是我们自定义的类加载器加载的,虽然是同一个Class文件但是却是独立的两个类,做的对象所属类型检查时结果就是false.
2.双亲委派模型
从java虚拟机角度来讲只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用c++实现(只限于HotSpot),是虚拟机自身的一部分;另一种是所有其他的类加载器,由java实现,独立于虚拟机外部,并且都继承自抽象类java.lang.ClassLoader。
从开发人员的角度来看,类加载器可以划分的更细致点,绝大部分java程序都会用到以下三种系统提供的类加载器。
1.启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用,在编写自定义类加载器是,如果需要把加载请求委派给引导类加载器,那直接用null代替即可。
2.扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
3.应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委派模型要求除了顶层启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器直接的父子关系不会以继承实现,是使用组合关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层启动类加载器中,只有当父加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己加载。(双亲委派模型不是强制性的约束模型)
使用双亲委派模型来组织类加载器直接的关系,显而易见的好处就是java类随它的类加载器一起具备了一种带有优先级的层次关系。如java.lang.Object,它存放在rt.jar.无论哪个类加载器加载这个类,最终都是委派给最顶端的启动类加载器进行加载。因此Object类在程序的各种类加载器环境中都是一个类。否则如果没有使用双亲委派模型,由各个类加载器自行加载,再程序的ClassPath中存放用户自己编写的java.lang.Object类。那系统中将会出现多个不同的Object类,应用程序将会一片混乱。
实现双亲委派的代码集中在java.lang.ClassLoader的loadClass()方法中:先检查是否已经被加载过,如果没有加载则调用父加载器的loadClass(),若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,抛出ClassNotFoundException异常后,再调用findClass方法进行加载。
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; } }
jdk1.8的loadclass方法。
3.“破坏”双亲委派模型
历史上一共有三次双亲委派模型被”破坏”的情况。
第一次是因为向前兼容JDK1.2以前的版本所添加的protected方法findClass(),在此之前用户去继承ClassLoader的唯一目的就是为了重写loadClass()方法。
第二次是为了能让基础类调用用户代码。如典型的例子JNDI,为了解决这个问题引入了线程上下文加载器,通过线程上下文使父类加载器请求子类加载器完成类加载,采用这种模式的例有:JNDI,JDBC,JCE,JAXB,JBI等(所有涉及SPI的加载动作几乎都采用这种模式)。
第三次是由于用户对程序动态性的追求导致的(代码热替换,模块热部署)。其中OSGI中对类加载器的使用十分值得学习。
(ps:因为篇幅的关系这里对破坏双亲委派模型这块不做过多讲述,有需要的同学请自行查询资料。如果发现有什么纰漏或是说错的地方请留言。内容采自深入理解java虚拟机 jvm高级特性与最佳实践)