目录:
- 类加载器:
- 什么是类加载器,它的作用是什么。
- 类加载器的分类及它们的作用。
- 如何实现一个自定义类加载器、自定义加载器的用途。
- 双亲委派:
- 什么是双亲委派。
- 为什么要使用双亲委派。
类加载器:
1、什么是类加载器,它的作用是什么。
类加载器就是把字节码文件加载到虚拟机中,即根据类的全限定名来获取该类的二进制字节流。
其核心源码如下:
1 package java.lang; 2 3 public abstract class ClassLoader { 4 5 /** 父加载器 */ 6 private final ClassLoader parent; 7 8 /** 加载类的核心方法之一,如果没找到类则抛出ClassNotFoundException */ 9 protected Class<?> loadClass(String name, boolean resolve) 10 throws ClassNotFoundException 11 { 12 // 1、同一时间只允许一线程加载名为name的类 13 synchronized (getClassLoadingLock(name)) { 14 // First, check if the class has already been loaded 15 // 2、加载前先检查是否已经加载了该类 16 Class<?> c = findLoadedClass(name); 17 // 3、c == null 表示没有被加载的类才会被加载 18 if (c == null) { 19 long t0 = System.nanoTime(); 20 try { 21 // 双亲委派:父加载器能加载的绝不给子类加载 22 if (parent != null) { 23 // 父加载器不为null,则把类加载的工作交给父加载器 24 c = parent.loadClass(name, false); 25 } else { 26 // 父加载器为null,则交给BootstrapClassLoader 27 c = findBootstrapClassOrNull(name); 28 } 29 } catch (ClassNotFoundException e) { 30 // ClassNotFoundException thrown if class not found 31 // from the non-null parent class loader 32 } 33 34 // 若父加载器不能加载,则子加载器尝试加载 35 if (c == null) { 36 // If still not found, then invoke findClass in order 37 // to find the class. 38 long t1 = System.nanoTime(); 39 // 子加载器尝试加载 40 c = findClass(name); 41 42 // this is the defining class loader; record the stats 43 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 44 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 45 sun.misc.PerfCounter.getFindClasses().increment(); 46 } 47 } 48 if (resolve) { 49 resolveClass(c); 50 } 51 return c; 52 } 53 } 54 55 }
ClassLoader.loadClass核心代码总结:
- 同一时间只允许一个线程加载名字为name的类。
- 在加载之前先检查,是否已经加载过该类,只有没有加载过的才允许加载。
- 双亲委派:父加载器能加载的绝不给子类加载。
- 如果父加载器加载不到,则交给子加载器加载(即:findClass)。
2、类加载器的分类及它们的作用。
从Java虚拟机角度来讲,只存在两种不同的类加载器:
- 一种是启动类加载器(Bootstrap ClassLoader),由C++语言实现,是虚拟机自身的一部分。
- 另一种是所有其他的类加载器,由Java语言实现,独立于虚拟机外部,全部继承抽象类java.lang.ClassLoader。
从开发人员角度来看,类加载器大致分四种:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。
启动类加载器(Bootstrap ClassLoader):负责将存放在lib目录中的,或被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的类库加载到虚拟机中。如rt.jar。名字不符合即使放在目录中也不被加载。如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
扩展类加载器(Extension ClassLoader):由sum.misc.Launcher$ExtClassLoader实现,负责加载<Java_Home>libext目录中的,或者被java.ext.dir系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):由sun.misc.Launcher$App-ClassLoader实现。是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。负责加载用户路径(ClassPath)上所指定的类库,如果应用程序中没有自定义过自己的类加载器,这个就是默认的加载器,开发人员可以直接使用这个类加载器。
3、如何实现一个自定义类加载器、自定义加载器的用途。
自定义加载器的实现其实就是重写loadClass方法。
1 public class UserClassLoader { 2 3 public static void main(String[] args) { 4 // 自定义类加载器 5 // 目的:打断默认的双亲委任机制 6 ClassLoader loader = new ClassLoader() { 7 @Override 8 // 自定义类加载器需要覆写loadClass函数 9 public Class<?> loadClass(String name) throws ClassNotFoundException { 10 try { 11 // 根据类名获取文件名 12 String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; 13 // 把文件转换为流 14 InputStream is = getClass().getResourceAsStream(fileName); 15 // 如果流为空,则交给父类调用loadClass去加载 16 if (is == null) { 17 return super.loadClass(name); 18 } 19 byte[] b = new byte[is.available()]; 20 // 把流转换为字节数组 21 is.read(b); 22 // 把字节码转化为Class并返回 23 return defineClass(name, b, 0, b.length); 24 } 25 catch (IOException e) { 26 throw new ClassNotFoundException(); 27 } 28 } 29 }; 30 } 31 32 }
自定义加载器有何用途:
- 加密:Java代码很容易被反编译,如果你需要对你的代码进行加密以防止反编译,则可以将编译后的代码用加密算法加密。但加密后的类就不能再使用Java默认的类加载器进行加载,这时候就需要自定义类加载器。
- 非标准的来源加载代码:字节码是放在数据库或者网络位置,需要自定义类加载器。
双亲委派:
1、什么是双亲委派。
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,因此所有的类加载请求最终都会传送到顶端的启动类加载器。
只有当父加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子加载器,子加载器会尝试去自己加载。
说白了:父加载器能加载的绝不给子加载器加载,只有父加载器找不到所需的类才让子加载器尝试加载。
2、为什么要使用双亲委派。
对任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。判断两个类是否"相等",必须是在这两个类被同一个类加载器加载的前提下。
基于双亲委派模型设计,那么Java中基础类,如Object类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被BootstrapClassLoader所响应。加载的Object类也会只有一个,否则如果用户自己编写了一个java.lang.Object类,并把它放到了ClassPath中,会出现很多个Object类,这样Java类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
最终目的:保证程序的安全,防止恶意篡改基类(如Object、List等等)。