JVM学习笔记(四、类加载器)

目录:

  • 类加载器:
    • 什么是类加载器,它的作用是什么。
    • 类加载器的分类及它们的作用。
    • 如何实现一个自定义类加载器、自定义加载器的用途。
  • 双亲委派:
    • 什么是双亲委派。
    • 为什么要使用双亲委派。

类加载器:

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等等)。

原文地址:https://www.cnblogs.com/bzfsdr/p/13623991.html