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

  • 相关阅读:
    最小生成树的解法
    51nod 1212 无向图最小生成树
    greater()和less()的使用
    51nod1183 编辑距离
    51nod 1181 质数中的质数(质数筛法)
    upper_bound和lower_bound的用法
    线段树最全模板
    bryce1010专题训练——线段树习题汇总
    51nod 1174 区间中最大的数
    51nod 1113 矩阵快速幂
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/13623991.html
Copyright © 2020-2023  润新知