IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 3.类加载流程 -> 正文阅读

[Java知识库]3.类加载流程

启动JVM来分析类加载流程

首先我们写一个简单的java程序

package com.jon;
import sun.security.pkcs11.P11Util;

/**
 * @author suym
 * @createTime 2021/7/23
 * @description
 */
public class StudyClassLoader{
    public static void main(String[] args) {
        System.out.println("Hello,World!");
        ClassLoader loader = P11Util.class.getClassLoader();
        System.out.println(loader);
    }
}

启动程序
可以在idea的vm options 添加 -verbose:class 来打印类加载的过程

程序的运行过程

windows开始启动java.exe程序,java.exe 程序将完成以下步骤:

  1. 根据JVM内存配置要求,为JVM申请特定大小的内存空间,这个内存将会成为程序的运行内存(可以通过jdk/bin中的jvisualvm.exe查看内存空间);
  2. 创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;
  3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader;
  4. 使用上述获取的ClassLoader实例加载我们定义的 com.jon.StudyClassLoader类;
  5. 加载完成时候JVM会执行StudyClassLoader类的main方法入口,执行StudyClassLoader类的main方法;
  6. 结束,java程序运行结束,JVM销毁。

JVM的运行内存分配

jvm内存空间

加载系统类

创建一个引导类加载器实例,初步加载系统类到内存方法区区域中
即{JRE_HOME}/lib下面的lib文件,此时JVM的内存空间如下图所示:
系统类加载完成后,JVM的内存空间

  1. 引导类加载器(Bootstrap Classloader)将系统类加载到运行内存后,对于某些特殊的类,方法区应该有运行时常量池,类型信息,字段信息,方法信息,类加载器引用,Class实例引用等
  2. 引导类加载器由于是通过c++语言实现,所以这些类的类加载器引用为null
  3. 对应Class实例引用,类加载器在加载类信息放在方法区后,会在heap中创建一个Class实例,作为开发人员访问方法区中引用类信息的入口

创建JVM启动类实例Launcher,并取得类加载器

当前jvm运行环境准备就绪,jvm将要调用sun.misc.Launcher的静态方法getLauncher(),获取Launcher实例;

//获取Java启动器
sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); 
//获取类加载器ClassLoader用来加载class到内存来
ClassLoader classLoader = launcher.getClassLoader();          

为了保证JVM只有一个Launcher实例,在Launcher内部使用了单例模式;在Launcher中还有两个类加载器,这两个类加载器分别是拓展类加载器(Extension ClassLoader) 和 应用类加载器(Application ClassLoader);
扩展类加载器用来加载{JRE_HOME}/lib/ext的jar文件,应用类加载器用来加载自定义的类
类加载器的功能
除了引导类加载器(Bootstrap Class Loader )的所有类加载器,都有一个能力,就是判断某一个类是否被引导类加载器加载过,如果加载过,可以直接返回对应的Class instance,如果没有,则返回null。图上的指向引导类加载器的虚线表示类加载器的这个有限的访问 引导类加载器的功能。
此时的 launcher.getClassLoader() 方法将会返回 AppClassLoader 实例,AppClassLoader将ExtClassLoader作为自己的父加载器
应用类加载器工作流程图
上面讨论的应用类加载器AppClassLoader的加载类的模式就是我们常说的双亲委派模型(parent-delegation model).
对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

  1. 委托父类加载器帮忙加载;
  2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
  3. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
  4. 若加载成功,返回 对应的Class 对象;若失败,抛出异常“ClassNotFoundException”。

请注意:
双亲委派模型中的"双亲"并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。上面的步骤中,有两个角色:

  1. 父类加载器(parent classloader):它可以替子加载器尝试加载类
  2. 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;换句话说,就是子类加载器不能接触到引导类加载器,引导类加载器对其他类加载器而言是透明的。双亲委托机制

类加载器加载main方法

通过 launcher.getClassLoader()方法返回AppClassLoader实例,接着就是AppClassLoader加载com.jon.StudyClassLoader类的时候了。

ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader类
classLoader.loadClass(“com.jon.StudyClassLoader”);//加载自定义类
上述定义的ocom.jon.StudyClassLoader类被编译成com.jon.StudyClassLoader class二进制文件,这个class文件中有一个叫常量池(Constant Pool)的结构体来存储该class的常量信息。
常量池信息
当AppClassLoader要加载 com.jon.StudyClassLoader类时,会去查看该类的定义,发现它内部声明使用了其它的类: sun.security.pkcs11.P11Util、java.lang.Object、java.lang.System、java.io.PrintStream、java.lang.Class;org.luanlouis.jvm.load.Main类要想正常工作,首先要能够保证这些其内部声明的类加载成功。所以AppClassLoader要先将这些类加载到内存中。(注:为了理解方便,这里没有考虑懒加载的情况,事实上的JVM加载类过程比这复杂的多)

类的加载顺序

  1. 加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
    AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class实例;
  2. 加载sun.security.pkcs11.P11Util
    此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,加载成功后直接返回对应的Class<sun.security.pkcs11.P11Util>实例;
  3. 加载com.jon.StudyClassLoader
    AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。而结果表明BootstrapClassLoader 没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,然后返回对应的Class<com.jon.StudyClassLoader>实例引用。
    jvm内存
    如上图所示:

JVM方法区的类信息区是按照类加载器进行划分的,每个类加载器会维护自己加载类信息;
某个类加载器在加载相应的类时,会相应地在JVM内存堆(Heap)中创建一个对应的Class,用来表示访问该类信息的入口。

使用StudyClassLoader类的main方法作为程序入口运行程序

调用StudyClassLoader类的main方法作为程序入口,开始执行程序。

方法执行完毕,JVM销毁,释放内存

方法执行完成后,jvm销毁,释放内存。

总结

类加载器

类加载器,顾名思义就是加载类的工具,当前jvm实现的主要有三种类加载器:引导类加载器(Bootstrap Class Loader)、拓展类加载器(Extension Class Loader )、应用加载器(Application Class Loader),我们也可以自己去自定义我们自己的类加载器

  1. 引导类加载器:该类主要有c++实现,用来加载jvm运行时所需要的lib下的系统类,由于是c语言实现,无法被java代码访问,但是我们可以查询类是否被引导类加载过。
  2. 拓展类加载器:由java实现,主要用来加载lib/ext下的拓展类,该类加载器是jvm中可以访问到的顶级父类加载器,它没有其他父类加载器。
  3. 应用类加载器:应用类加载器主要作为加载用户程序的类,会将拓展类加载器当成自己的父类,使用双亲委托机制加载类
  4. 自定义类加载器:继承java.lang.ClassLoader

双亲委托机制

    //提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class<T> instance 实例
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class<T>实例
            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();
                    // 自己尝试加载
                    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;
        }
    }

流程图如下
双亲委托机制流程图
双亲委托机制

  1. 引导类加载器先加载完系统类,然后获取Lanucher实例,使用应用类加载器进行加载类;
  2. 应用类加载器加载类文件,如果已经加载直接返回,如果没有加载,判断是否由父类加载器,如果有,让父类加载器先去加载(父类加载器会先判断是否已经加载过这个类文件,如果已经加载直接返回,如果没有加载,继续判断是否有父类加载器)以此保证ext的类都是由拓展类加载器进行加载。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:30:08  更:2021-07-29 11:32:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 6:33:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码