首页 > 代码库 > java类加载器——ClassLoader
java类加载器——ClassLoader
Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载。
如果你平时只是做web开发,那应该很少会跟类加载器打交道,但如果你想深入学习tomcat服务器的架构,它是必不可少的。所谓类加载器,就是用于加载Java类到Java虚拟机中,它负责读取Java字节码,并转换成java.lang.Class类的一个实例,使字节代码.class文件得以运行。一般类加载器负责根据一个指定的类找到对应的字节代码,然后根据这些代码定义成一个Java类,另外还负责加载资源,包括图像文件和配置文件。
类加载器在实际使用中给我们带来的好处是,它可以使Java类动态地加载到JVM并运行,即可在程序运行时再加载类,提供了很灵活的动态加载方式。例如我们熟悉的Applet,从远程服务器下载字节码到客户端动态加载到JVM便可以运行。
在Java的庞大体系中,可以将系统分为三种类加载器,分别是:
① 启动类加载器(Bootstrap ClassLoader):加载对象是Java核心库,把一些关键的Java类加载进JVM,这个加载器使用原生代码(C/C++)实现的,并不是继承java.lang.ClassLoader,它是所有其他类加载器的最终父加载器,负责加载<JAVA_HOME>/jre/lib目录下且被JVM指定的类库,其实它属于JVM整体的一部分,JVM一启动就将这些指定的类加载到内存中,避免以后过多的I/O操作,提高系统的运行效率。启动类加载器无法被Java程序直接使用。
② 扩展类加载器(Extension ClassLoader):加载的对象为Java的扩展库,即加载<JAVA_HOME>/jre/lib/ext目录里面的类。这个类由上面的Bootstrap ClassLoader加载,但由于Bootstrap ClassLoader并非用Java实现,已经脱离了Java体系,所以如果尝试调用扩展类加载器的getParent()方法获取父加载器会得到null,但它的父类加载器是Bootstrap ClassLoader。Java中可以直接使用扩展类加载器。
③ 应用程序类加载器(Application ClassLoader):亦叫系统类加载器(System ClassLoader),它负责加载用户类路径(CLASSPATH)指定的类库,如果程序没有自己定义类加载器,就默认使用应用程序类加载器。它也由Bootstrap ClassLoader加载,但它的父加载类被设置成了Extension ClassLoader。如果要使用这个加载器,可通过ClassLoader.getSystemClassLoader()获取。
假如有一天你心血来潮也想自己写一个类加载器,那么你只需要继承java.lang.ClassLoader类即可。于是可以用下面的图2-4-1来清晰表示出各种类加载器的关系,Bootstrap ClassLoader是最根本的类加载器,其不存在父类加载器,Extension ClassLoader由Bootstrap ClassLoader加载,所以它的父类加载器是Bootstrap ClassLoader,Application ClassLoader也是由Bootstrap ClassLoader加载,但它的父加载器被指向了Extension ClassLoader,而其他所有用户自定义的类加载器都由Application ClassLoader加载。
由此可以看出越重要的类加载器就越早被JVM载入,这是考虑到安全性,因为先加载的类加载器会充当下一个类加载器的父加载器,在双亲委派模型机制下,就能确保安全性。
那么什么是双亲委派模型?比如爷爷、爸爸、你三代单传,你们家族都遗传懒惰基因,每当遇到事情都互相推脱,现在需要一个人去买一包盐,不然今晚的菜就有色无味了,而你第一个被委托去超市买,但由于你的懒惰,你向你爸爸撒了一顿娇,你爸爸受不了你只能答应帮你去,在懒惰的驱使下,你爸爸最后找了一个借口说要赶工作,让你爷爷出去走动走动顺便买一包盐,就这样你爷爷只能乖乖去超市买盐。双亲委派模型就类似这样的机制,类加载器加载类时首先委托给父类加载器加载,除非父类加载器不能加载才自己加载。
这种模型要求除了顶层的启动类加载器外,其他的类加载器都要有自己的父类加载器。假如有一个类要加载进来,一个类加载器并不会马上尝试自己将其加载,而是委派到父类加载器,父类加载器收到后又尝试委派到其父类加载器,以此类推,直到委派到启动类加载器,这样一层一层往上委派。只有当父类加载器反馈自己没法完成这个加载时,子加载器才会尝试自己加载。通过这个机制,保证了Java应用所使用的都是同一个版本的Java核心库的类。同时这个机制也保证了安全性,设想如果Application ClassLoader想要加载一个有破坏性的java.lang.System类,双亲委派模型会一层层向上委派,最终委派给Bootstrap ClassLoader,而Bootstrap ClassLoader检查到缓存中已经有了这个类,并不会再加载这个有破坏性的System类。
另外,类加载器还拥有全盘负责机制,即当一个ClassLoader加载一个类时,这个类所依赖的、引用的其他所有类都由这个ClassLoader加载,除非在程序中显性地指定另外一个ClassLoader加载。
图2-4-1 类加载器关系
在Java中,我们用完全匹配类名来标识一个类,即用包名和类名。而在JVM中,一个类由完全匹配类名和一个类加载器的实例ID作为唯一标识。即是说同一个虚拟机可以有两个包名、类名都相同的类,只要他们由两个不同类加载器加载。于是当我们在Java中经常说两个类相不相等,必须是针对同一个类加载器加载的前提下才有意义,否则就算是同样的字节代码,由不同类加载器加载,这两个类也不是相等的。这种特征为我们提供了隔离机制,在tomcat服务器中是十分有用的。
了解了JVM的类加载器的各种机制后,看看一个类是怎样被类加载器载入进来的。一个类准备加载,类加载器先判断此类是否已经被加载过(加载过的类会被缓存在内存中),如果缓存中存在此类则直接返回这个类。否则获取父类加载器,如果父类加载器为null,则由Bootstrap ClassLoader载入并返回Class。如果父类加载器不为null,则由父类加载器载入,载入成功就返回Class,载入失败则根据类路径查找class文件,找到就加载此class并返回Class,找不到就抛出ClassNotFoundException异常。
图2-4-2 类加载过程
类加载器属于JVM级别的设计,我们很多时候基本不会跟他打交道,但是假如你想了解整个JAVA体系是如何工作的,假如你要设计开发自己的框架或中间件或一个较庞大的java软件,那么你必须熟悉类加载器的相关机制,在现实的设计中,根据实际情况利用类加载器可以提供类库的隔离及共享,保证你的软件不同级别的逻辑分割程序不会互相影响,提供更好的安全性。