类加载器会在加载阶段将class文件加载到方法区。关于类加载的整个过程,可以先参考我的另一篇文章《类的奇妙漂流——探索类加载机制》ClassLoader(ApplicationClassLoader)启动类加载器是嵌入在JVM内核中的加载器,用C++语言编写(因此不会继承ClassLoader),是类加载器层次结构中的最顶层加载器。用于加载java的核心类库,即加载jre/lib/rt.jar中的所有类。由于启动类加载器涉及到虚拟机本地的实现细节,我们无法获取到启动类加载器的引用。扩展类加载器负责加载JRE的扩展目录jre/lib/ext或者java.ext.dirs系统属性指定目录下的jar包类。父类加载器是启动类加载器,但是用扩展类加载器调用getParent还是null。应用类加载器也称为系统类加载器,可以通过java.lang.ClassLoader.getSystemClassLoader()方法获取该类加载器的实例,因此得名系统类加载器。应用类加载器主要加载类路径下的类,即用户编写的应用程序编译的类,调用getParent返回扩展类加载器。扩展类加载器和应用类加载器的继承结构如图所示:可以看到除了启动类加载器外,其他两个类加载器都继承自ClassLoader。我们自定义的类加载也需要继承自ClassLoader。双亲委托机制当一个类加载器收到一个类加载请求时,它不会首先尝试加载该类,而是将请求转发给父类加载器,这一点对于每一层类加载器都是一样的。因此所有的类加载请求都应该传递给最顶层的启动类加载器。只有当父类加载器在自己的加载范围内找不到类,并向子类反馈无法加载时,子类加载器才会尝试自行加载。ClassLoader中的loadClass方法很好的解释了双亲委托的加载方式:protectedClass>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//检查类是否已经被当前类加载器LoadedClass>c=findLoadedClass(name);if(c==null){//类还没有加载try{if(parent!=null){//如果父加载器不为null,则委托到父类去加载c=parent.loadClass(name,false);}else{//如果父加载器为null,说明当前类加载器已经是启动类加载器,使用启动类加载器来直接去Loadtheclassc=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){}if(c==null){//此时父类加载器无法加载该类,则使用当前类加载器加载longt1=System.nanoTime();c=findClass(name);...}}//是否需要连接这个类if(resolve){resolveClass(c);}returnc;}}为什么要用双亲委派机制,直接用当前的类加载器加载不就行了吗?为什么这么复杂?假设现在没有双亲委托机制,有这样一个场景:用户写了一个Student类,点击运行。编译完成后,虚拟机开始加载类,由应用程序加载器加载。由于Object类是Student的父类,不存在双亲委派机制,应用加载器会尝试自己加载Object类,而用户根本没有定义Object,即应用加载器无法在加载范围内搜索到这个类,所以此时无法加载Object类,用户编写的代码无法运行。假设用户定义了一个Object类,再次运行后,应用类加载器会正常加载用户自定义的Object类和Student类。Student类将调用System.out.print()来输出Student对象。这个时候System类会被启动类加载器加载,Object类也会在这之前被加载。此时方法区有两份Object元数据,重复加载Object类!如果用户自定义的Object类不安全,可能会直接导致虚拟机崩溃或引发重大安全问题。如果现在使用双亲委托机制,虽然用户自己定义了Object类,可以编译,但是永远不会记录在方法区。双亲委托机制避免了重复加载,保证了虚拟机的安全性。自定义类加载器我们整理一下ClassLoader中加载类的过程:判断是否加载过,使用双亲委派模型,请求父加载器,父加载器反馈加载不了,所以使用findclass,让当前类加载器找到findclass:当前类加载加载器根据类文件的路径和名称加载字节码,从类文件中读取字节数组,然后使用defineClassdefineclass:根据字节数组返回Class对象。我们在ClassLoader中找到findClass方法,发现这个方法直接抛出异常,应该留给子类去实现。protectedClass>findClass(Stringname)throwsClassNotFoundException{thrownewClassNotFoundException(name);}到这里,我们应该明白loadClass方法使用的是模板方法模式,主要逻辑是双亲委托,但是如何转换类的步骤文件到一个Class对象中已经由子类来实现了。对模板方法模式不熟悉的同学可以参考我的另一篇文章模板方法模式。其实在源码中,已经有自定义类加载的示例代码了。在注释中:classNetworkClassLoaderextendsClassLoader{Stringhost;intport;publicClassfindClass(Stringname){byte[]b=loadClassData(name);returndefineClass(name,b,0,b.length);}privatebyte[]loadClassData(Stringname){//loadtheclassdatafromtheconnection}}可以看出,如果我们需要自定义类加载器,只需要继承ClassLoader,重写findClass方法即可。现在有一个简单的例子,类文件还在文件目录下:packagecom.yang.testClassLoader;importsun.misc.Launcher;importjava.io.*;publicclassMyClassLoaderextendsClassLoader{/***类加载路径,不包含文件name*/privateStringpath;publicMyClassLoader(Stringpath){super();this.path=path;}@OverrideprotectedClass>findClass(Stringname)throwsClassNotFoundException{byte[]bytes=getBytesFromClass(name);assertbytes!=null;//读取bytes数组,转成Class对象returndefineClass(name,bytes,0,bytes.length);}//读取class文件,转成byte数组privatebyte[]getBytesFromClass(Stringname){StringabsolutePath=path+"/"+name+".class";FileInputStreamfis=null;ByteArrayOutputStreambos=null;try{fis=newFileInputStream(newFile(absolutePath));bos=newByteArrayOutputStream();byte[]temp=newbyte[1024];intlen;while((len=fis.read(temp))!=-1){bos.write(temp,0,len);}returnbos.toByteArray();}catch(IOExceptione){e.printStackTrace();}最后{if(null!=拳头){try{fis.close();}catch(IOExceptione){e.printStackTrace();}}if(null!=bos){try{bos.close();}catch(IOExceptione){e.printStackTrace();}}}returnnull;}publicstaticvoidmain(String[]args)throwsClassNotFoundException、IllegalAccessException、InstantiationException{MyClassLoaderclassLoader=newMyClassLoader("C://develop");Classtest=classLoader.loadClass("Student");test.newInstance();}}学生类:publicclassStudent{publicStudent(){System.out.println("studentclassloaderis"+this.getClass().getClassLoader().toString());}}注意,这个Student类不要加上包名,如果idea报错就忽略它,然后用javacStudent.java编译这个类,将生成的class文件复制到c://develop中运行main方法MyClassLoader,可以看到输出:可以看出Student.class确实被我们自定义的类加载器加载了。销毁双亲委托从上面自定义类加载器的内容,我们应该可以猜到,销毁双亲委托直接重写loadClass方法就结束了。其实我们确实可以重写loadClass方法,毕竟这个方法没有被final修饰过。既然双亲委派是有好处的,为什么jdk会开放重写loadClass呢?这要看双亲委托是什么时候引入的:双亲委托模型是在JDK1.2之后引入的,类加载器和抽象类java.lang.ClassLoader在JDK1.0时代就已经存在了。面对现有的用户自定义类加载器的实现代码,Java设计者在引入双亲委派模型时不得不做出一些妥协。在此之前,用户继承java.lang.ClassLoader的唯一目的就是重写loadClass()方法。jdk为了向前兼容,不得不开启loadClass的rewrite操作。当然,这并不是双亲委派模式受到的唯一损害。详细文章可参考双亲委派模型的破坏。其中提到了一个“线程上下文类加载器”。对此不熟悉的同学可以参考真正理解线程上下文类加载器(多例分析)(无法贴出链接,百度搜索)我们经常使用的Tomcat和jdbc破坏了双亲委派。由于文章篇幅和博主水平,这里暂时不讨论破坏的原因。有兴趣的同学可以参考这篇文章。JDBC和Tomcat为什么要破坏双亲委派模型?(无法贴出链接,百度搜索)
