类加载器加载Class文件的过程

类加载器加载Class文件的过程

jdk8和9有一些区别,这里以8为准,9作为最后的扩充

类加载器是用于加载class文件的,我们从这里开始介绍

前言

因为底层硬件的不同,如果在不同硬件上都要做一次适配化无疑是令人奔溃的,如何让代码一次编译,处处运行呢。

计算机工程领域的任何问题都可以通过增加一个中间层来解决,字节码应运而生 Bytecode。

Java所有的指令有200个左右,一个字节(8位)可以存储236种不同的指令信息,JVM通过将字节码解释执行,屏蔽底层依赖。

Class文件

这是最简单的一段程序

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello JVM");
    }
}

这是它对应的字节码文件

cafe babe 0000 0034 0022 0a00 0600 1409
0015 0016 0800 170a 0018 0019 0700 1a07
001b 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 104c 636c 6173 7374
6573 742f 4d61 696e 3b01 0004 6d61 696e
0100 1628 5b4c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b29 5601 0004 6172 6773
0100 135b 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 0100 0a53 6f75 7263 6546
696c 6501 0009 4d61 696e 2e6a 6176 610c
0007 0008 0700 1c0c 001d 001e 0100 0948
656c 6c6f 204a 564d 0700 1f0c 0020 0021
0100 0e63 6c61 7373 7465 7374 2f4d 6169
6e01 0010 6a61 7661 2f6c 616e 672f 4f62
6a65 6374 0100 106a 6176 612f 6c61 6e67
2f53 7973 7465 6d01 0003 6f75 7401 0015
4c6a 6176 612f 696f 2f50 7269 6e74 5374
7265 616d 3b01 0013 6a61 7661 2f69 6f2f
5072 696e 7453 7472 6561 6d01 0007 7072
696e 746c 6e01 0015 284c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5600 2100
0500 0600 0000 0000 0200 0100 0700 0800
0100 0900 0000 2f00 0100 0100 0000 052a
b700 01b1 0000 0002 000a 0000 0006 0001
0000 0009 000b 0000 000c 0001 0000 0005
000c 000d 0000 0009 000e 000f 0001 0009
0000 0037 0002 0001 0000 0009 b200 0212
03b6 0004 b100 0000 0200 0a00 0000 0a00
0200 0000 0b00 0800 0c00 0b00 0000 0c00
0100 0000 0900 1000 1100 0000 0100 1200
0000 0200 13

看上去还是云里雾里的很正常,不要畏难,拆分的看下去

cafe babe是Gosling定义的一个魔法数,意思是CoffeeBaby十进制表示3405691582

它的作用是标志这是一个Java类文件,如果没识别到这个,说明他不是java的类文件或者文件已经损坏,无法进行加载。

0000 0034代表当前版本号

如果对编译原理感兴趣的同学可以 javap -verbose Main.class 来读字节码文件 (idea这个插件查看 jclasslib Bytecode Viewer)

这里就先不深究

image-20220306183733579

Class文件的执行模式

  1. 解释执行

  2. JIT编译执行

  3. JIT编译与解释混合执行(主流JVM默认执行方式)

    混合模式的优势在于解释器在启动时先解释执行,省去编译时间。

    在运行期间JVM通过热点代码统计分析,识别高频调用的方法循环与公共模块,基于JIT动态编译,将热点代码转换成机器码直接交给CPU执行。

    JIT流程图如下

在这里插入图片描述

实际生产环境中使用JIT要注意,刚启动的JVM均是解释执行,如果流量过高可能会假死。

建议每次发布生产环境时分为 生产环境机器总数/8=发布总批次数

类加载过程

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载 (Loading)、验证(Verification)、

准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)七个阶段,

其中验证、准备、解析三个部分统称 为连接(Linking)。这七个阶段的发生顺序如图7-1所示。(有些版本描述也说是五个部分,就是把连接的三个部分都连起来并成一个阶段)

image-20220306190435675

这里有一个口诀帮助记忆

家宴准备了西式菜

在这里插入图片描述

解析不一定是按这个顺序走的:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

其他流程顺序是固定的

接下来分别探究每一个过程分别做了什么

加载

1.根据类的全类名来获取定义此类的二进制字节流。

2.并将字节流所代表的静态存储结构转换为特定的运行时数据结构

3.在内存中生成一个代表这个类的java.lang.Class实例对象

加载过程会校验cafe babe魔法数,常量池,文件长度,是否有父类等

连接

验证

这一步主要是为了安全,做更为详细的验证

文件格式验证

​ 这里验证结束后这段字节流才被允许进入Java虚拟机内存的方法区中进行存储,后面三步是在此基础上验证的

元数据验证

​ 除了java.lang.Object之外,所有的类都应当有父类就是这里验证的

字节码验证

​ 保证不会出现类似于“在操作 栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况。

符号引用验证

​ 可访问性(private、protected、public、)是否可被当前类访问。

准备

类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初 始值的阶段

解析

将常量池内的符号引用替换为直接引用的过程

方法的引用,java.lang.NoSuchMethodError就是这个位置抛出的

初始化

Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。

执行类构造器方法

类加载器

参考上一篇文章中 保证Java程序的稳定运作

它确保了内存中类的唯一性

先看层级结构

在这里插入图片描述

写代码验证

public class Main {
    public static void main(String[] args) {
        Operation operation = new Operation();

        // sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(operation.getClass().getClassLoader());
        // sun.misc.Launcher$ExtClassLoader@1b6d3586
        System.out.println(operation.getClass().getClassLoader().getParent());
        // null
        System.out.println(operation.getClass().getClassLoader().getParent().getParent());
    }

}

第三个应该打印BootstrapClassLoader,为什么会是个null?

因为BootstrapClassLoader是通过C/C++实现的,不存在JVM体系中所以输出为null,

类加载器具有登记制度但是并没有继承关系,以组合的方式复用父加载器的功能

  • 附加
    在这里插入图片描述
    类加载器源码图解
    在这里插入图片描述

    JDK9中用平台加载器替代了扩展加载器的功能

image-20220306214930839

image-20220306214946449
参考资料
《码出高效:Java开发手册》 杨冠宝(孤尽) 高海慧(鸣莎) p100

《深入理解Java虚拟机:JVM高级特性与最佳实践》(第3版)7.4 类加载器

公众号:JAVA技术圈子 欢迎关注,分享读书笔记
https://mp.weixin.qq.com/s/svHfkKsHlzwaNv0S-flNPQ

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值