类加载器与ClassFileFormate

JVM / JRE / JDK

JVM

  • Java 虚拟机(Java Virtual Machine)它是运行所有 Java 程序的虚拟计算机
  • 所有的 Java 程序会首先被编译为 .class 的类文件,这种类文件可以在虚拟机上执行
  • Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点
  • 所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器
  • 因此,Java 程序无须重新编译便可在多种不同的计算机上运行

JRE

  • JRE 是 Java runtime environment(Java运行环境)
  • 光有 JVM 还不能让 class 文件执行,因为在解释 class 的时候 JVM 需要调用解释所需要的类库 lib
  • jre 目录里面有两个文件夹 binlib,在这里可以认为 bin 里的就是 jvm,lib 中则是 jvm 工作所需要的类库,而 jvm 和 lib 和起来就称为 jre

JDK

  • JDK 是 java development kit(java 开发工具包)
  • 程序员做 Java 开发时所需要的一些工具 javac.exe / jar.exe,Java 基础的类库 / API

关系

  • JDK 包含 JRE,而 JRE 包含 JVM

Java 编译过程

image-20210731113445805

.class → 机器码

  • 在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢
  • 有些方法和代码块是经常需要被调用的,也就是所谓的热点代码
  • 后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后
  • 其会将字节码对应的机器码保存下来,下次可以直接使用
  • 这也解释了我们为什么经常会说 Java 是编译与解释共存的语言

类加载过程

  • 在上面调用 java.exe 时,需要把类给加载到 JVM 中,使用类加载器进行加载

编译到加载整体流程

image-20210731113743114

类加载过程

  • 在硬盘上查找并通过 IO 读入字节码文件,加载到 JVM 内存的方法区当中

方法区中存放的哪些信息分别如下

  • 方法区中的内容:类被加载到方法区中后主要包含,运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应 class 实例的引用等信息
  • 类加载器的引用:类加载器属于是一个实例对象,存放在 中,在方法区中指向类加载器的地址
  • class 实例的引用:类加载器在加载类信息放到方法区中后,这些字节码信息加在一起,有共同的特征和行为,会创建一个对应的 Class 类型的实例对象存放到堆中

验证

  • 校验字节码文件的正确性
  • 编译的字节码,有可能被修改,在加载之前要验证字节码的正确性

准备

  • 给类的静态变量分配内存,并赋予默认值
  • 静态变量是在类加载的时候就被执行的

解析

  • 将符号引用替换为直接引用,该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄等 (直接引用) 这个过程也称为是 静态链接
  • 静态链接过程 (类加载期间完成)
  • 动态链接是在程序运行期间完成的将符号引用替换为直接引用

初始化

  • 对类的静态变量初始化为指定的值,执行静态代码块

image-20210731131829589

类加载器

什么是类加载器 classLoader

  • 负责将 .class 文件加载到内存中,并为之生成对应的 Class 对象
  • 虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行

类加载器分类

Bootstrap 根类加载器

  • 也被称为引导类加载器,负责 Java 核心类的加载
  • 比如 SystemString 等。在 JDK 中 JRE 的 lib 目录下 rt.jar 文件中
  • c++ 来实现

Extension 扩展类加载器

  • 负责 JRE 的扩展目录中 jar 包的加载
  • 在 JDK 中 JRE 的 lib 目录下 ext 目录
  • sun.misc.Launcher$ExtClassLoader 实现
  • 负责加载 <JAVA_HOME>\lib\extjava.ext.dirs 系统变量指定的路径中的所有类库
  • 加载一些扩展的系统类,比如 XML、加密、压缩相关的功能类等

jdk9 的时候引入平台类加载器,废除了扩展类加载器

  • JDK8 的主要加载 jrelibext,扩展 jar 包时使用
  • 这样操作并不推荐,所以废除。而 JDK9 有了模块化,更无需这种扩展加载器
  • 加载一些平台相关的模块,比如 java.scriptingjava.compiler*java.corba*

App 应用程序类加载器

  • 负责在 JVM 启动时加载来自 java 命令的 class 文件
  • 以及 classpath 环境变量所指定的 jar 包和类路径

自定义加载器

  • 负责加载用户自定义路径下的类包

各种类加载器代码演示,首先看 jdk1.8 在 1.8 当中还是叫扩展类加载器,看完了 1.8 在看 jdk11

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) {
        // 根类加载器 c++ 实现的,打印为 null,因为它并不是属于 java 语言当中的
        System.out.println(String.class.getClassLoader());
        
        // 扩展类加载器
        System.out.println(DNSNameService.class.getClassLoader());

        // 应用程序类加载器
        System.out.println(Client.class.getClassLoader());
    }
}

image-20210810110405236

如上 jdk1.8 的内容看完之后在 idea 当中切换 jdk 到 11:

image-20210810110557816

切换之后发现 DNSNameService 找不到了,在 11 之后就变为了 平台类加载器 如下:

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) {
        // 根类加载器 c++ 实现的,打印为 null,因为它并不是属于 java 语言当中的
        System.out.println(String.class.getClassLoader());

        // 平台类加载器
        System.out.println(JavaCompiler.CompilationTask.class.getClassLoader());

        // 应用程序类加载器
        System.out.println(Client.class.getClassLoader());
    }
}

image-20210810111516522

双亲委派

  • 加载某个类时会先委托父加载器寻找目标类,找不到,再委托上层父加载器加载
  • 如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类

image-20210731161244011

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) {
        // 根类加载器,C++
        System.out.println(String.class.getClassLoader());

        // 扩展类加载器
        System.out.println(DNSNameService.class.getClassLoader());

        // 应用程序类加载器
        System.out.println(Client.class.getClassLoader());

        // 自定义加载器略

        System.out.println("----------------------华丽的分割线----------------------");

        // 获取应用程序类加载器的父加载器(扩展类加载器)
        System.out.println(Client.class.getClassLoader().getParent());

        // 获取应用程序类加载器的父加载器的父加载器(根类加载器, C++)
        System.out.println(Client.class.getClassLoader().getParent().getParent());
    }
}

image-20210810113939643

🐤为什么要有双亲委派,为了系统类的安全,jvm 如何认定两个对象同属于一个类型

  • 都是用同名的类完成实例化的
  • 两个实例各自对应的同名的类的加载器必须是同一个
  • 比如两个相同名字的类,一个是用系统加载器加载的,一个扩展类加载器加载的,两个类生成的对象将被 jvm 认定为不同类型的对象

为了系统类的安全,类似 java.lang.Object 这种核心类,jvm 需要保证他们生成的对象都会被认定为同一种类型,基于双亲委派模型设计,那么 Java 中基础的类,类似 Object 类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被 Bootstrap ClassLoader 所响应,加载的 Object 类也会只有一个,否则如果用户自己编写了一个 java.lang.Object 类,并把它放到了 ClassPath 中,会出现很多个 Object 类,这样 Java 类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。

🐱‍👓避免类的重复加载

  • 当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次
  • 保证被加载类的唯一性

类加载源码分析

  • 获取字节码对象

image-20210801093528544

在类被加载到内存的时候创建的对象,通过此对象操作字节码信息

加载字节码

image-20210801094204330

image-20210801094255313

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 查找有没有加载过
        Class<?> c = findLoadedClass(name);

        // 如果没有加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {

                // 让父加载器进行加载
                if (parent != null) {

                    // 父加载同样调用此方法,进行加载
                    c = parent.loadClass(name, false);
                } else {

                    // 如果父为空, 就直接调用bootstrap根加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 略
            }
            if (c == null) {
                // 如果都没有找到
                long t1 = System.nanoTime();

                // 自己去找 如果想要自定义classload 只须要重写findClass
                // 模板方法设计模式
                c = findClass(name);
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

类加载器父子关系源码分析

image-20210801094619776

自定义类加载器

image-20210815231556267

/**
 * @author BNTang
 */
public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("d:/java/test/", name.replace(".", "/").concat(".class"));

        try {
            FileInputStream fis = new FileInputStream(f);

            int length = fis.available();
            byte[] data = new byte[length];

            fis.read(data);
            fis.close();

            // 把二进制流转成Class类对象
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // throws ClassNotFoundException
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> clazz = myClassLoader.loadClass("top.it6666.Person");
        Method show = clazz.getMethod("show");
        show.invoke(null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

image-20210815231456474

image-20210801102908643

posted @ 2021-07-25 09:14  BNTang  阅读(119)  评论(0编辑  收藏  举报