当前位置: 首页 > news >正文

Java 的类加载机制

01、字节码

计算机只认识 0 和 1,所以任何语言编写的程序都需要编译成机器码才能被计算机理解,然后执行,Java 也不例外。

在不同平台(Windows、Linux)上运行的 Java 虚拟机(JVM)——负责载入和执行 Java 编译后的字节码。

通过 源码.java->(编译器)字节码.class->JVM

02、类加载过程

Java 的类加载过程可以分为 5 个阶段:载入、验证、准备、解析和初始化。这 5 个阶段一般是顺序发生的,但在动态绑定的情况下,解析阶段发生在初始化阶段之后。

1)Loading(载入)

JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

2)Verification(验证)

JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。

  • 确保二进制字节流格式符合预期(比如说是否以 cafe bene 开头)。
  • 是否所有方法都遵守访问控制关键字的限定。
  • 方法调用的参数个数和类型是否正确。
  • 确保变量在使用之前被正确初始化了。
  • 检查变量是否被赋予恰当类型的值。

3)Preparation(准备)

JVM 会在该阶段对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。

也就是说,假如有这样一段代码:

public String string = "变量";
public static String static_string = "静态变量";
public static final String final_string = "不可以变量";

string 不会被分配内存,而 strtic_string 会;但 static_string 的初始值不是“静态变量”而是 null

需要注意的是,static final 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 final_string 在准备阶段的值为“不可变量”而不是 null

4)Resolution(解析)

该阶段将常量池中的符号引用转化为直接引用。

what?符号引用,直接引用?

符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。

在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 com.Wanger 类引用了 com.Chenmo 类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址,因此只能使用符号 com.Chenmo

直接引用通过对符号引用进行解析,找到引用的实际内存地址。

5)Initialization(初始化)

该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。

上面这段话说得很抽象,不好理解,举个例子。

String string = new String("zhangvalue");

上面这段代码使用了 new 关键字来实例化一个字符串对象,那么这时候,就会调用 String 类的构造方法对 string 进行实例化。

03、类加载器

聊完类加载过程,就不得不聊聊类加载器。

一般来说,Java 程序员并不需要直接同类加载器进行交互。JVM 默认的行为就已经足够满足大多数情况的需求了。不过,如果遇到了需要和类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就不得不花大量的时间去调试
ClassNotFoundException 和 NoClassDefFoundError 等异常。

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

站在程序员的角度来看,Java 类加载器可以分为三种。

1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。

2)扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。

3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。

通过一段简单的代码了解下。

public class Test {

    public static void main(String[] args) {
        ClassLoader loader = Test.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader.toString());
            loader = loader.getParent();
        }
    }

}

每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 类名.class.getClassLoader() 可以获取到此引用;然后通过 loader.getParent() 可以获取类加载器的上层类加载器。

这段代码的输出结果如下:

第一行输出为 Test 的类加载器,即应用类加载器,它是 sun.misc.Launcher$AppClassLoader 类的实例;第二行输出为扩展类加载器,是 sun.misc.Launcher$ExtClassLoader 类的实例。那启动类加载器呢?

按理说,扩展类加载器的上层类加载器是启动类加载器,但在当前这个1.8版本的 JDK 中, 扩展类加载器的 getParent() 返回 null。所以没有输出。

04、双亲委派模型

如果以上三种类加载器不能满足要求的话,程序员还可以自定义类加载器(继承 java.lang.ClassLoader 类),它们之间的层级关系如下图所示。

这种层次关系被称作为双亲委派模型:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。

PS:双亲委派模型突然让我联想到朱元璋同志,这个同志当上了皇帝之后连宰相都不要了,所有的事情都亲力亲为,只有自己没精力没时间做的事才交给大臣们去干。

使用双亲委派模型有一个很明显的好处,那就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。

上文中曾提到,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等——双亲委派模型能够保证同一个类最终会被特定的类加载器加载。

相关文章:

  • K-means算法
  • 如何管理自己的情绪
  • 记录win10装jdk
  • 按照开发阶段划分测试技术
  • 数据倾斜
  • Tomcat9启动乱码
  • @GetMapping和@RequestMapping的区别
  • 正则表达式-匹配A和B之间字符串
  • HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed out.
  • urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
  • Mac下Chromedriver存放位置
  • 解决 Cannot open pip-script.py
  • Python安装docx库
  • Windows下Chromedriver存放位置
  • Python中str跟int的转换
  • 5、React组件事件详解
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CAP理论的例子讲解
  • Consul Config 使用Git做版本控制的实现
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • JavaScript创建对象的四种方式
  • Java的Interrupt与线程中断
  • JS函数式编程 数组部分风格 ES6版
  • MaxCompute访问TableStore(OTS) 数据
  • Redis 懒删除(lazy free)简史
  • Redis中的lru算法实现
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Spring Cloud Feign的两种使用姿势
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 基于HAProxy的高性能缓存服务器nuster
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 前端面试之闭包
  • 网页视频流m3u8/ts视频下载
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 写给高年级小学生看的《Bash 指南》
  • kubernetes资源对象--ingress
  • #android不同版本废弃api,新api。
  • #FPGA(基础知识)
  • #QT(一种朴素的计算器实现方法)
  • (2)(2.10) LTM telemetry
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (二)Eureka服务搭建,服务注册,服务发现
  • (二)springcloud实战之config配置中心
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (学习日记)2024.02.29:UCOSIII第二节
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (转)Linq学习笔记
  • .bashrc在哪里,alias妙用
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .NET MVC之AOP
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .NetCore 如何动态路由
  • .NET微信公众号开发-2.0创建自定义菜单