• JVM类加载


    前言

    在我们写一个.java 文件时,这个文件是怎么被处理的呢。Java 可以解释执行也可以编译执行,大多数JVM采用第三种混合的方式。冯诺依曼体系的计算机模型中,任何程序都需要加载到内存中才能和CPU进行交流。.java文件被编译成.class的字节码文件之后交给JVM执行时也同样需要被加载到内存中,才可以实例化类。

    JVM的类加载机制指的是虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。简单来说就是将.class字节码文件实例化成Class对象并进行相关初始化的过程。

    1. 类加载过程

    通过双亲委派模型来进行类加载的,其中的重要部分就是类加载器。类加载器对类进行加载(Loding)、连接(Linkind)、初始化(Init)。其中连接阶段指的是验证、准备、解析这三部分。

    加载:

    Load 阶段读取类文件产生二进制流,并转化为特定的数据结构,初步校验 cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应类的 java.lang.Class 实例。

    连接:

    • 验证: 验证是更详细的校验,比如final 否合规、类型是否正确、静态变量是否合理等
    • 准备: 准备阶段是为静态变量分
      配内存,并设定默认值
    • 解析: 解析指找到相关引用,进行相关的类加载。

    初始化: Init 阶段执行类构造器<clinit> 方法

    类加载器有哪些

    • 启动类加载器(Bootstrap ClassLoader)
      • 使用 C++ 语言实现,是虚拟机自身的一部分
      • 负责<JAVA_HOME>lib 目录中的

    下面的类加载器都是由 Java 语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader

    • 拓展类加载器(Extension ClassLoader)

      • 负责加载<JAVA_HOME>libext 目录
    • 应用程序类加载器(Application ClassLoader)

      • 负责加载用户路径上所指定的类库
    • 自定义类加载器(User ClassLoader)

      • 开发者自己拓展定义的

    2. 双亲委派模型

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把则会个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求都是应该传送到顶层的类加载器去处理,只有当父加载器反馈自己无法完成这个加载(它的搜索范围内没有找到所需的类时),子类才会去尝试自己去加载。

    双亲委派模型是如何实现的

    java.lang.ClassLoader 中的loadClass()方法

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                // 检查类是否被加载了
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    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.
                        long t1 = System.nanoTime();
                        // 调用自身的 findClass 方法来进行类加载
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    双亲委派模型的好处就是Java类会有一种带有优先级的层次关系,比如java.lang.String 它存放在rt.jar 中 无论那个类加载都会往上级询问,也就是最终到BootStrap类加载器进行加载。也就是说如果你自己实现一个名为java.lang.String的类,并放在程序的ClassPath中,可以正常编译,但无法被加载运行。(如果可以的话,程序就会产生混乱了)。

    自定义类加载器:

    public class MyClassLoader extends ClassLoader {
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] result = getClassFromMyPath(name);
            try {
                if (result == null) {
                    throw new FileNotFoundException();
                } else {
                    return defineClass(name, result, 0, result.length);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            throw new ClassNotFoundException();
        }
    
        private byte[] getClassFromMyPath(String name) {
            // 从自定义路径中加载指定类
            return null;
        }
    
        public static void main(String[] args) {
            MyClassLoader classLoader = new MyClassLoader();
            try {
                Class<?> clazz = Class.forName("Student", true, classLoader);
                Object obj = clazz.getInterfaces();
                System.out.println(obj.getClass().getClassLoader());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    ctrl + alt + t idea 快捷 try catch

    3. 那种情况需要自定义类加载器

    • 隔离加载类

      • 在某些框架内进行中间件与应用的模块隔离 把类加载到不
        同的环境。比如 阿里内某容器框架通过自定义类加载器确保应用中依赖的 jar 包不
        会影响到中间件运行时使用的 jar 包。
    • 修改类加载方式

      • 类的加载模型并非强制 Bootstrap 其他的加载并非一
        定要引入 或者根据实际情况在某个时间点进行按需进行动态加载。
    • 扩展加载源

      • 比如从数据库、网 ,甚 是电视机机顶盒进行加载
    • 防止源码泄露

      • Java 代码容易被编译和篡改,可以进行编译加密 那么
        加载器也需要自定义,还原加密的字节码。

    Reference

    • 《深入理解Java虚拟机》
    • 《码出高效》
  • 相关阅读:
    为什么Java中 wait 方法需要在 synchronized 的方法中调用?
    XML常用解析API有哪几种?
    Dubbo 和 Spring Cloud 的区别?
    Java 线程池中 submit() 和 execute()方法有什么区别?
    详细描述一下 Elasticsearch 搜索的过程?
    为表中得字段选择合适得数据类型 ?
    Json有什么作用?
    Ajax的乱码解决问题?
    eclipse安装配置记录
    srs部署/webrtc拉流
  • 原文地址:https://www.cnblogs.com/wei57960/p/12782902.html
Copyright © 2020-2023  润新知