前言本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制?今天想跟大家唠嗑唠嗑 Java 的类加载机制,这是 Java 的一个很重要的创新点,曾经也是 Java 流行的重要原因之一。 Oracle 当初引入这个机制是为了满足 Java Applet 开发的需求, JVM 咬咬牙引入了 Java 类加载机制,后来的基于 Jvm 的动态部署,插件化开发包括大家热议的热修复,总之很多后来的技术都源于在 JVM 中引入了类加载器。
如今,类加载机制也在各个领域大放异彩,在面试中,由类加载机制所衍生出来各类面试题也层出不穷。 所以,我们要了解下类加载机制,为工作中或者是面试中实际的需要打好良好的基础。 二.核心知识点归纳2.1 概述Q1:JVM类加载机制定义:虚拟机把描述类的数据从 Class 文件 加载 到内存,并对数据进行 校验 、 转换解析 和 初始化 ,最终形成可被虚拟机直接使用的 Java 类型的过程 Q2:特性运行期类加载。即在 Java 语言里面,类型的加载、连接和初始化过程都是在程序 运行期 完成的,从而通过牺牲一些性能开销来换取 Java 程序的高度灵活性 JVM 运行期动态加载+动态连接-> Java 的动态扩展特性
2.2 类加载的过程类从被加载到虚拟机内存中开始、到卸载出内存为止,整个生命周期包括七个阶段: 其中,验证、准备、解析这3个部分统称为 连接 ,流程如下图: 注意: Java 想要了解 Java 动态绑定和静态绑定区别的话,可以看下这篇文章: 理解静态绑定与动态绑定 2.2.1 加载Q1:任务想要详细了解类的全限定名的知识,可以看下这篇文章: 全限定名、简单名称和描述符是什么东西? 2.2.2 验证由此可见,它能直接决定 JVM 能否承受恶意代码的攻击,因此验证阶段 很重要 ,但由于它对程序运行期没有影响,并 不一定必要 ,可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 检验过程包括下面四个阶段: A.文件格式验证: B.元数据验证: C.字节码验证: D.符号引用验证: 内容:对 类自身以外(如常量池中的各种符号引用)的信息 进行匹配性校验 目的:确保解析 动作能正常执行 ,如果无法通过符号引用验证,那么将会抛出一个 java.lang.IncompatibleClassChangeError 异常的子类 注意:该验证发生在虚拟机将 符号引用 转化为 直接引用 的时候,即『 解析 』阶段
保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现“在操作数栈的数据类型中放置了 int 类型的数据,使用时却按 long 类型来载入本地变量表中” 保证任何跳转指令都不会跳转到方法体外的字节码指令上 ......
类是否有父类(除了 java.lang.Object 之外,所有类都应有父类) 父类是否继承了不允许被继承的类( final 修饰的类) 如果该类不是抽象类,是否实现了其父类或接口中要求实现的所有方法 ......
是否以魔数 0xCAFEBABE 开头 主次版本号是否在 JVM 接受范围内 索引值是否有指向不存在/不符合类型的常量 ......
2.2.3 准备Q1:任务2.2.4 解析之前提过,解析阶段就是虚拟机将 常量池 内的 符号引用替换为直接引用 的过程 类或接口 ( CONSTANT_Class_info ) 字段 ( CONSTANT_Fieldref_info ) 类方法 ( CONSTANT_Methodref_info ) 接口方法 ( CONSTANT_InterfaceMethodref_info ) 方法类型 ( CONSTANT_MethodType_info ) 方法句柄 ( CONSTANT_MethodHandle_info ) 调用点限定符 ( CONSTANT_InvokeDynamic_info )
举个例子,设当前代码所处的为类 D ,把一个从未解析过的 符号引用 N 解析为一个 类或接口 C 的直接引用 ,解析过程分三步: 若 C 不是数组类型: JVM 将会把代表 N 的全限定名传递给 D 类加载器去加载这个类 C 。在加载过程中,由于 元数据验证 、 字节码验证 的需要,又可能触发其他相关类的加载动作。一旦这个加载过程出现了任何异常,解析过程就宣告失败。 若 C 是数组类型且数组元素类型为对象: JVM 也会按照上述规则加载数组元素类型 若上述步骤无任何异常:此时 C 在 JVM 中已成为一个有效的类或接口,但在解析完成前还需进行 符号引用验证 ,来确认 D 是否具备对 C 的访问权限。如果发现不具备访问权限,将抛出 java.lang.IllegalAccessError 异常
Q1:字段(成员变量/域)和属性有什么区别?class Person{ private String mingzi; //mingzi是字段,一般来说字段和属性是相同的,但是这个例子是特例 public String getName(){ //name是属性 return mingzi: } public void setName(){ mingzi= '张三'; }} 2.2.5 初始化clinit clinit :由编译器自动收集类中的所有 类变量(静态变量)的赋值动作 和静态语句块 static{} 中的语句合并产生
是 线程安全 的,在多线程环境中被正确地加锁、同步 对于类或接口来说是 非 必需的,如果一个类中 没有静态语句块 ,也没有对变量的赋值操作,那么编译器可以不为这个类生成 clinit 接口与类不同的是 ,执行接口的 clinit 不需要先执行父接口 的 clinit ,只有当父接口中定义的变量使用时,父接口才会初始化。另外, 接口的实现类在初始化时 也一样不会执行接口的 clinit
想详细了解 clinit 以及其与 init 的区别的读者,可以看下这篇文章: 深入理解jvm--Java中init和clinit区别完全解析 遇到 new 、 getstatic 、 putstatic 或 invokestatic 这4条字节码指令时 使用 java.lang.reflect 包的方法对类进行反射调用的时候 当初始化一个类的时候,若发现其父类还未进行初始化,需先触发其父类的初始化 在虚拟机启动时,需指定一个要执行的 主类 ,虚拟机会先初始化它 当使用 JDK1.7 的动态语言支持时,若一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法句柄,且这个方法句柄所对应的类未进行初始化,需先触发其初始化。
2.3 类加载器&双亲委派模型每个类加载器,都拥有一个独立的命名空间,它不仅用于加载类,还和这个类本身一起作为在 JVM 中的唯一标识。所以比较两个类是否相等,只要看它们是否由同一个 类加载器 加载,即使它们来源于同一个 Class 文件且被同一个 JVM 加载,只要加载它们的 类加载器不同,这两个类就必定不相等 2.3.1 类加载器从 JVM 的角度,可将类加载器分为两种: 扩展类加载器A.由 sun.misc.Launcher$ExtClassLoader 实现 B.负责加载 <JAVA_HOME>\lib\ext 目录中的、或者被 java.ext.dirs 系统变量所指定的路径中的所有类库 应用程序类加载器A.是 默认 的类加载器,是 ClassLoader#getSystemClassLoader() 的返回值,故又称为 系统类加载器 B.由 sun.misc.Launcher$App-ClassLoader 实现 C.负责加载用户类路径上所指定的类库 自定义类加载器:如果以上类加载起不能满足需求,可自定义
需要注意的是:虽然 数组类 不通过类加载器创建而是由 JVM 直接创建的,但仍与类加载器有密切关系,因为 数组类的元素类型最终还要靠类加载器去创建 2.3.2 双亲委派模型定义:表示类加载器之间的层次关系 前提 :除了顶层启动类加载器外, 其余类加载器都应当有自己的父类加载器 ,且它们之间关系一般不会以 继承 关系来实现,而是通过 组合 关系来复用父加载器的代码 工作过程 :若一个类加载器收到了类加载的请求,它先会把这个请求 委派 给父类加载器,并向上传递,最终请求都传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载 注意 :不是一个强制性的约束模型,而是 Java 设计者推荐给开发者的一种类加载器实现方式 优点 :类会随着它的类加载器一起具备带有 优先级 的层次关系,可保证 Java 程序的稳定运作;实现简单,所有实现代码都集中在 java.lang.ClassLoader的loadClass() 中
比如,某些类加载器要加载 java.lang.Object 类,最终都会委派给最顶端的启动类加载器去加载,这样 Object 类在程序的各种类加载器环境中都是同一个类。 相反,系统中将会出现多个不同的 Object 类, Java 类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱 三.课堂小测试恭喜你!已经看完了前面的文章,相信你对 JVM 类加载机制已经有一定深度的了解,下面,进行一下课堂小测试,验证一下自己的学习成果吧! Q1:类加载的全过程是怎样的? Q2:什么是双亲委派模型? Q3: String 类如何被加载的 上面问题的答案,在前文都提到过,如果还不能回答出来的话,建议回顾下前文 Q4:请你谈谈类加载过程,以 Person a = new Person(); 为例进行说明 这道题是在牛客的暑假实习 Tencent 一面的面筋上找的,附上标准答案: 类的加载过程,Person person = new Person();为例进行说明 如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力 本文参考链接:
|