• [JVM]类加载的过程(学习总结)


    一、引入

    我们所写的代码就相当于:

    一组 *.java 文件 + 一组资源文件(多么媒体文件、配置文件)+ 相关存储中的数据(MySQL、Redis...)

    我们现在的程序数据放在硬盘中,要让运行时电脑运行我们的程序,实际就是让该电脑的CPU 运行我们程序中的指令数据。

    但CPU 无法直接和硬盘(IO设备)做直接的数据交换

    CPU 只能和内存中的数据打交道,现在数据又放在硬盘中

    所以我们应该先把数据从硬盘中读取到内存中。

    这个过程是以类为单位(某个*.class 文件)进行读取的

    一次一个类文件的加载,按需进行加载(用到哪个加载哪个)

    这个过程就被称为 类的加载(Class Load)。这个加载过程也得依赖某些指令来执行,这些指令(程序)就被称为类的加载器(ClassLoader)。

    这些指令是属于 JVM 程序的一部分。换言之, JVM 中有进行必要的类的加载的职责。

    二、类文件中的数据是怎么组织的(什么东西放在哪)

    javac.exe 编译器是 A 电脑上进行的,靠着 JVM 的规范来约束

    java.exe   JVM(运行期)是 B 电脑上进行

    1.JDK

    用于给 Java 开发人员使用的小包裹

    1. 提前准备好的程序(*.exe / *.dll)编译器、调试工具:编译器、调试工具、运行时的分析工具
    2. 官方提供的所有人都能使用的类文件 *.class

    2.JRE:Java运行时的环境

    用于给一般用户运行好别人写好的 Java 程序(以 *.class 为代表的数据文件,可以是*.jar / *.war...)的一组环境

    1. JVM(Java Virtual Machine) Java虚拟机:java.exe
    2. 运行期间支持运行的一组官方类文件 *.class

    开发人员也会用到 JRE (不可能光开发,不去运行测试)

    三、类文件中主要有哪些部分的数据

    class file 格式一直在更新

    javac.exe 5编译的类能不能用java.exe 17运行

    1.8  :让编译器按照1.8(Java8)的标准检查 java 文件语法

    1.8  :编译出来的类文件里,写明是 1.8(Java8)版本

    常量池(很多常量)、类的基本属性、方法(构造、静态构造代码块)、签名 + 指令(语句)、字符串(字面量)、数字、符号

    类文件的数据 = 类的信息 + 常量池 + 方法(方法的信息 + 指令(字节码形式))

    ClassLoader 要加载一个类,主要就是要加载这些数据到内存中。 

    四、JVM 会按需进行类的加载,什么情况下会加载一个类?

    大前提:用到了一个类,但是这个类还不在内存中,就需要加载(如果已经在内存中,就没必要进行第二次加载)

    什么叫用到:

    1. 使用一个类,进行对象的实例化时(构造对象时)
    2. 使用一个类时,会触发使用这个类的父类(父类:类的继承、接口的实现、接口的继承)
    3. 使用静态的内容(静态属性、静态方法)

    class Main { psvm() { ... } }

    java Main 

    以 Main 类作为主类启动 JVM 。需要加载 Main 类,因为用到了 Main 类的静态方法(main 方法)

    Object类 也需要加载,因为 Object 是 Main 的父类

    五、类加载期间,ClassLoader 主要做了哪些工作?

    类、接口、枚举、注解都在这块称为“类”

    1.Loading(加载)

    根据要加载的类名,找到对应的类文件( *.class)

    验证类文件合法,没有错误(还得考虑安全问题,有可能写恶意的类也加载进来)

    解析数据(按照规范格式)

    2.Linking(链接)

    类里面用到很多用字符串字面量写死的数据,比如“java/lang/Object”

    但实际程序(JVM)执行中,需要的java.lang.Object 在内存中对应的数据

    所以要把 com.lingqi.demo.Main 和 java.lang.Object 根据字面量“链接”起来

    3.Initializing(初始化)

    将类放到内存的指定位置后,进行类里的必要数据的初始化(主要是静态属性)

    执行类的初始化工作

    站在Java开发者的角度:我们的代码中的哪些东西是在这个阶段执行的?静态属性的初始化(赋值操作)、静态代码块 static { ... }

    1. class Main {
    2. static int a = 10; // = 10 是赋值的操作,是类的初始化阶段要做的
    3. static int b = callStaticMethod(); // = callStaticMethod() 操作,是类的初始化阶段要做的
    4. // 导致这个阶段,会去调用 callStaticMethod 方法
    5. static { // 静态代码块/静态构造代码块
    6. System.out.println("hello"); // 这里的所有语句,是类的初始化阶段要做的
    7. }
    8. static int callStaticMethod() {
    9. return 2; // 这个方法会被执行,只是因为 b = callStaticMethod() 导致的
    10. }
    11. static int someStaticMethod() {
    12. return 1; // 这个方法不会被执行
    13. }
    14. public static void main(String[] args) {
    15. String s = "hello";
    16. System.out.println(s);
    17. }
    18. }

     一定是先执行父类的初始化完成之后,才会进行子类的初始化。

    六、关于类

    1.类名:

    俗称 :Main

    权威类名:com.lingqi.demo.Main。更深一层就是 JVM 进行类的加载时,保证一个类只会在内存中存在一份(粗略地可以当成类只被加载一次(类是可以被卸载的))

    JVM 内部:类加载器 + 权威类名,确定一个类是否存在

    2.默认情况下有那些类加载器?

    不同的类,由不同的类加载(因为类和类的地位的不平等)

    Boostrap ClassLoader(启动类加载器):加载 SE 下的标准类(java.lang.String、java.util.List、java.io.InputStream)

    Extenttion ClassLoader(扩展类加载器):加载 SE 下的扩展类

    Application ClassLoader(应用类加载器):我们写类、我们通过 maven 或者其他工具引入的第三方类

    举个例子:

    如果有人提个要求,说让我加载一个类,那么我们怎么知道要加载的这个类的.class文件放在哪里呢?(比如说 rt.jar)

    ClassLoader 肯定输入 JRE,所以知道自己被安装在哪 

                                                                                    C:\Program Files\Java\jdk1.8.0_131\jre

    所以去固定位置去找C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar

    对于应用类加载器Application ClassLoader来说:

    必须告诉它去哪里找,通过 ClassPath 变量告诉。java-classpath=路径,或者放在环境变量 ClassPath 中。  路径1/路径2/路径3。  ClassLoader 先去路径1下找,没找到再去 2 下,没找到再去 3,最后没找到,就会报异常(ClassNotFound,NoClassDef)

    3.类的数据被加载到内存的什么位置了?

    JVM 会管理一片内存(这片内存,JVM 是哪弄来的?JVM 是普通进程,从OS 申请过来的)

     内存分的区域:

    1. PC 寄存器(每个线程存在自己下一条指令的地址)
    2. JVM 栈(Java Virtual Machine Stacks)
    3. 本地方法栈 Native Method Stacks(和JVM 栈一起称为栈区)
    4. 堆(Heap)
    5. 方法区(Mathod Area)
    6. Run-Time Constant Pool(运行时常量池)

    不同的JVM 实现中,可以有自己的实现。比如我们现在使用的都是 Oracle 提供的 Hotspot JVM。

    除了上述的区域,还有一部分直接管理的内存,一般称为原生内存


    类中的数据:类的信息、方法、常量池数据。

    类的信息和方法组织成一个东西,叫做类信息,常量池其实也是类的信息。逻辑上,类信息被存到了方法区。常量池存在了运行时的常量池中,它也是类,实际上逻辑上也可以理解为,这也是方法区的一部分。

    那么,我们简单理解就是,整个类的数据,都被放在了方法区之中了(基本信息、方法(所有的方法)、常量数据、静态属性)。但是这个阶段是不讨论具体的JVM 实现的。不同版本的 JVM 的具体实现实际上是不同的。所以深入探讨的时候,还需要根据实际情况来具体的分析。


    七、小结

    1.类文件放在哪里?

    硬盘中,以文件的形式出现最为常见

    2.类文件是怎么来的

    经过编译器,将 java 源文件编译而来的

    3.类文件中的主要数据有?

    按照规范,主要有 基本信息(静态属性)、方法(方法信息、指令(字节码))、常量

    4.为什么进行类的加载?

    按照冯诺依玛体系,CPU 无法直接读取硬盘中的数据,需要先加载到内存中

    5.为什么类文件要按需加载,并且以类为单位加载?

    相对来说,节省内存空间,实现方便

    6.类名是什么

    ①权威类名 = 包名 + 类名             ② 类加载器 + 权威类名

    7.什么时候会去加载类(什么时候用到了一个类)?

    实例化对象、访问静态属性、调用静态方法、子类调用到父类

    8.类在内存中只会存在一份

    正确

    9.类的加载过程

    加载、链接、初始化

    10.类的初始化时会执行我们的哪些代码?

    ①属性的初始化赋值    ②静态构造代码块    父类的初始化一定在子类之前完成  按照书写顺序

    11.类被加载到内存的什么位置?

    逻辑上,放在方法区。但不同 JVM 的实现,可以有进一步讨论空间

    12.默认的类加载器有哪些?

    启动类加载器、扩展类加载器、应用类加载器

    13.加载时,ClassLoader 怎么知道一个类对应的类文件所在?

    启动、扩展类加载器根据固定位置找。应用类加载器,根据class path 的路径依次查找

    14.如果加载时,一个类不存在,会出现异常(ClassNotFound、NoClassDef...)

    八、双亲委派机制(⭐)

    默认的三个类加载器之间遵守一个规范:

    1.三个类加载器之间存在

    2.Application ClassLoader  需要加载类的时候,先委派给双亲去加载

    如果 parent 加载成功这个类了,你就不用加载了;否则自己再去加载

    3.目的是放置加载进来用户写好的恶意代码

    前提:一个类(A类)用到了其他类(B C D类),则其他的这些类(B C D )的加载动作,默认是由当时加载 A类的加载器来加载。

    比如说com.lingqi.demo.Main 用到了 java.lang.String 类

    1. public static void main(String[] args){
    2. String s = "hello";
    3. System.out.println(s);
    4. }

     但是我还定义了一个自己的String 类

     那么我用的时候用的是 rt.jar 下的java.lang.String 还是我自己定义的?

    那么双亲委派机制是如何解决这个问题的

    1.com.lingqi.demo.Main 类被哪个类加载?—— ApplicationClassLoader 去加载

    2.Main 用到了 java.lang.String ,则默认让 ApplicationClassLoader 去加载。

    3.如果没有双亲委派,加载的就是我们自己写的 java.lang.String

    4.如果有双亲委派,则 ApplicationClassLoader 优先让 BootStrapClassLoader 去加载,能加载到是 rt.jar 下的。所以不会加载我们自己写的。

    这就是双亲委派的意义。

  • 相关阅读:
    爬取基金收盘价并用pyecharts进行展现
    什么是代理IP池?如何判断IP代理商的IP池是否真实优质?
    ECcube 使用负载平衡器时无法识别 HTTPS/SSL 时的响应,链接都变成 http
    B站韩顺平的正则表达式学习
    蓝牙耳机哪款好用音质好?高音质的蓝牙耳机推荐
    理解Linux32位机器下虚拟地址到物理地址的转化
    【算法挨揍日记】day30——300. 最长递增子序列、376. 摆动序列
    如果把网络原理倒过来看,从无到有,一切都清晰了(上)
    高中生可发表论文的学术期刊涵盖TCR历史期刊
    Redis 面试题汇总(不定期更新)
  • 原文地址:https://blog.csdn.net/weixin_61567666/article/details/126027182