zoukankan      html  css  js  c++  java
  • 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虚拟机》
    • 《码出高效》
  • 相关阅读:
    Lucene.Net 2.3.1开发介绍 —— 二、分词(一)
    控制‘控制台应用程序’的关闭操作
    详解for循环(各种用法)
    敏捷软件开发
    Sql Server的一些知识点
    在SharePoint 2010 中配置Remote Blob Storage FILESTREAM Provider
    使用LotusScript操作Lotus Notes RTF域
    JOpt Simple 4.5 发布,命令行解析器
    John the Ripper 1.8.0 发布,密码破解工具
    PacketFence ZEN 4.0.1 发布,网络接入控制
  • 原文地址:https://www.cnblogs.com/wei57960/p/12782902.html
Copyright © 2011-2022 走看看