类加载器的功能:通过一个类的全限定名来获取描述此类的二进制字节流的过程
java的类加载器大致可以分为两类,一类是系统提供的,一类是由应用开发人员编写的。系统提供的类加载器有以下三种:
引导类加载器(bootstrap class loader):用来加载 Java 的核心库(rt.jar),是用原生代码来实现的,并不继承自 java.lang.ClassLoader
。
扩展类加载器(extensions class loader):用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
应用(系统)类加载器(Application class loader):根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
另外还有开发人员自定义的Class loader。
系统在进行类加载时,会使用parent delegation原则。
parent delegation原则的工作过程:如果一个类加载器需要加载某个类时,它不会自己去尝试加载这个类,而是新该加载请求委托给其父类加载器去完成。同样父类加载器也会将该请求委托给其父类,直到顶层的Bootstrap Class Loader,引导类加载器根据请求,尝试加载类文件。如果加载类文件失败,则会反向由其子加载器去尝试加载,直到能加载成功的子加载器为止。简化后的顺序如下:
自定义类加载器->应用类加载器->扩展类加载器->引导类加载器
parent delegation的优势是什么?
通过parent delegation的方式,可以保证,java程序的稳定器,java判断是否是相同类,会要求两个类必须由同一类加载器加载。如果没有parent delegation的原则,对于同一个类,可能经由多个类加载器去加载。而每个类加载器加载到的类,被虚拟机认定为不同类,可想而知,这样会有多少操作和方法都不能执行。
parent delegation的实现代码?
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { 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. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
如以上代码所示,parent delegation的实现非常简单,首先findLoadedClass(调用native方法)查找类是否已经被加载,如果类未被加载,则通过parent. loadClass(name, false);加载类,只有父加载器未加载成功,才会通过当前类加载器去加载。
特例-线程上下文类加载器:
对于大多数的类加载都可以通过parent delegation的方式去现实,但有没有特例情况呢?如:jndi接口位于rt.jar包中,rt属于java核心类,其加载都通过bootstrap class loader完成。但jndi的实现,是由外部提供,不属于rt中。非rt包中的类,对于bootstrap class loader是不可见的,是无法加载的。那在加载jndi接口的同时,怎么样去加载jndi的实现呢?这时,可以通过线程上下文类加载器去实现,线程上下文类加载器并不是一个真正的类加载器,它是相对当前类加载器来说的。
把线程上下文类加载器定义成一种方式更合适,线程上下文类加载器其实就是一种可以在父类加载器的环境下,调用子类加载器的方式。表面上看起来似乎与parent delegation有冲突,其实不然,父类还是无法直接调用子类加载器的,能调用子类加载器的,只是在当前类加载器是父类加载器的线程环境,通过当前线程的getContextClassLoader()方法,去获取其它类加载器。示例如下:
1 package com.csair.soc.thread; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 6 public class MyClassLoader extends ClassLoader{ 7 @Override 8 public Class<?> loadClass(String name) throws ClassNotFoundException { 9 try { 10 String fileName = name.substring(name.lastIndexOf("." ) + 1) 11 + ".class"; 12 InputStream is = this.getClass().getResourceAsStream(fileName); 13 if (is == null) { 14 return super.loadClass(name); 15 } 16 byte[] b = new byte[is.available()]; 17 is.read(b); 18 return defineClass(name, b, 0, b. length); 19 } catch (IOException e) { 20 throw new ClassNotFoundException(name); 21 } 22 } 23 }
1 package com.csair.soc.thread; 2 3 public class ThreadContextTest { 4 public static void main(String[] args) throws ClassNotFoundException { 5 ThreadTest tt = new ThreadTest(); 6 //设置线程的上下文类加载器 7 tt.setContextClassLoader( new com.csair.soc.thread.MyClassLoader()); 8 tt.start(); 9 } 10 public static class ThreadTest extends Thread{ 11 public void run() { 12 try { 13 System. out.println( "线程上下文类加载器:" + getContextClassLoader()); 14 System. out.println( "当前类加载器:" +this.getClass().getClassLoader()); 15 Class<?> class1 = getContextClassLoader().loadClass("com.csair.soc.thread.ThreadContextTest" ); 16 System. out.println( "加载class的类加载器:" +class1.getClassLoader()); 17 } catch (ClassNotFoundException e) { 18 e.printStackTrace(); 19 } 20 } 21 } 22 }
输出结果:
线程上下文类加载器:[email protected]
当前类加载器:[email protected]
加载class的类加载器:[email protected]
通过线程上下文类加载器就能解决jndi的加载问题了,在当前类加载器为bootstrap class loader的线程环境下,线程通过getContextClassLoader()方法,获取提前设置的线程类加载器(Application class loader),去加载jndi的实现类。
classLoader怎么样持有实体类?为什么classLoader卸载后,实体类也跟着被卸载?
classloader会有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。对于class的卸载,Sun公司的原话是这么说的:"class or interfacemay be unloaded if and only if its class loader is unreachable. Classesloaded by the bootstrap loader may not be unloaded.当某个类的加载器被gc回收之后,该类才能被卸载
Class.forname()和ClassLoader.loadClass有什么区别,首次new XXX(),是怎么加载class的?
java提供两个方法来加载类
1、隐匿加载,如new XXX()方式来隐式加载class
2、显示加载,java.lang.Class里的forName()方法,java.lang.ClassLoader里的loadClass()
forName实际上调用native方法,forName0(className, true , ClassLoader.getCallerClassLoader ());
通过当前类加载器,来加载className。第二个参数为true表示装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量。
而使用loadClass加载类时,默认是不初始化该类的。
类加载器符合parent delegation的原则,那自定义的类加载器,为什么能加载class?
自定义类加载器,可以重写loadClass不遵守parent delegation原则。另外,有些类文件对于系统的类加载器不可见,或者说系统的类加载器找不到类文件,此时,自定义类加载器通过自定义的findClass方法去找到类文件。