Java类加载器层次结构原理解析
作者:青葱岁月
类加载器的层次结构:
引导类加载器(bootstrap class loader)
用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的(C实现的),并不继承自java.lang.ClassLoader。
加载扩展类和应用程序类加载器,并指定它们的父类加载器。
扩展类加载器(extensions class loader)
用来加载java的扩展库(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路径下的内容)java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。
有sun.miscLauncher$ExtClassLoader实现,继承自java.lang.ClassLoader
应用程序类加载器(application class loader)
它根据java应用的类路径(classpath,java.class.path路径)来加载指定路径的类,一般来说,java应用的类都是由它来完成加载的
由sun.misc.Launcher$AppClassLoader实现,继承自java.lang.ClassLoader
自定义类加载器
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
说明:在java中由于类的加载采用的是双亲委托机制,上面几种类加载器是父子关系,其中引导类加载器为基础。
ClassLoader类介绍
作用:
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个java类,即java.lang.Class类的一个实例。
除此之外,ClassLoader还负责加载java应用所需的资源文件,如图像文件和配置文件等。
相关方法:
- getParent() 返回该类加载器的父类加载器
- loadClass(String name) 加载名称为name的类,返回的结果是java.lang.Class类的实例
- findClass(String name) 查找名称为name的类,返回的结果是java.lang.Class类的实例
- findLoadedClass(String name) 查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例
- defineClass(String name,byte[] b,int off,int len) 把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final的。
- resolveClass(Class<?> c) 链接指定的java类。
代码测试类加载器:
public class Demo02 { public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent());; System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());; } }
输出:
sun.misc.Launcher$AppClassLoader@1016632
sun.misc.Launcher$ExtClassLoader@dc6a77
null
依次为应用加载器、扩展加载器和引导加载器(但是引导加载为原生代码所写,因此获取不到,为null)。
类加载器的代理模式:
代理模式:交给其他加载器来加载指定的类。
双亲委托机制:
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,以此追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委托机制是为了保证java核心库的类型安全(这种机制就保证不会出现用户自己能定义java.lang.Object类的情况)。
类加载器除了用于加载类,也是安全的最基本的屏障。
双亲委托机制是代理模式的一种:
并不是所有的类加载器都采用双亲委托机制。
tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试自己去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。
自定义类加载器的流程:
继承:java.lang.ClassLoader
首先检查请求的类型是否已经被这个类装载器装载到命名空间中,如果已经装载,则返回
委派类将加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例
调用本类加载器的findClass()方法,师徒获取对应的字节码,如果获取得到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其它原因失败,则返回异常给loadClass(),loadClass()转抛异常,终止加载过程
注:被两个加载器加载的同一个类,Jvm不认为是相同的类。
示例代码如下:
package com.test; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * 自定义文件系统加载器 * @author We.lxk * */ public class FileSystemClassLoader extends ClassLoader{ private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } private byte[] getClassData(String classname){ //com.test.User -> rootDir/com/test/User String path = rootDir +"/"+classname.replace(".", "/")+".class"; //IOUtils 可以使用它将读取的流数据转换为字节数组 InputStream is = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try{ is = new FileInputStream(path); byte[] buffer = new byte[1024]; int temp = 0; while((temp=is.read(buffer))!=-1){ baos.write(buffer, 0, temp); } return baos.toByteArray(); }catch(Exception e){ e.printStackTrace(); return null; }finally{ try { if(is!=null) is.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(baos!=null) baos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); //应该先查询有没有加载过这个类。已经加载,则直接返回加载好的类。 if(c!=null){ return c; }else{ ClassLoader parent = this.getParent(); try{ //System.out.println("hello"); c = parent.loadClass(name); //委派给父类加载 }catch(Exception e){ //e.printStackTrace(); } if(c!=null){ return c; }else{ byte[] classData = getClassData(name); if(classData==null){ throw new ClassNotFoundException(); }else{ c = defineClass(name, classData, 0, classData.length); } } } return c; } }
测试代码:
package com.test; /** * 测试自定义的FileSystemClassLoader * @author We.lxk * */ public class Demo03 { public static void main(String[] args) throws Exception { FileSystemClassLoader loader = new FileSystemClassLoader("D:/myJava"); FileSystemClassLoader loader2 = new FileSystemClassLoader("D:/myJava"); Class<?> c = loader.loadClass("com.test.Demos"); Class<?> c2 = loader.loadClass("com.test.Demos"); Class<?> c3 = loader2.loadClass("com.test.Demos"); Class<?> c4 = loader2.loadClass("java.lang.String"); Class<?> c5 = loader.loadClass("com.test.Demo"); System.out.println(c.hashCode()+" "+c.getClassLoader()); System.out.println(c2.hashCode()+" "+c2.getClassLoader()); System.out.println(c3.hashCode()+" "+c3.getClassLoader()); System.out.println(c4.hashCode()+" "+c4.getClassLoader()); System.out.println(c5.hashCode()+" "+c5.getClassLoader()); //System.out.println(.getClassLoader()); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。