class对象和class文件_JVM——Class文件的加载

前言

关于class文件的加载机制之前写过文章,这里对其进行一些补充,基于《深入理解java虚拟机》。

先贴个图回顾下类的加载流程

f1307621bed006bb7b1b9d600a999938.png

1.验证

如果代码足够可靠,可以用-Xverify:none可以关闭大部分的验证

1.1 文件格式验证

·是 否 以 魔 数 0xCAFEBABE 开 头。

·主、 次 版 本 号 是 否 在 当 前 Java 虚 拟 机 接 受 范 围 之 内。

·常 量 池 的 常 量 中 是 否 有 不 被 支 持 的 常 量 类 型( 检 查 常 量 tag 标 志)

·指 向 常 量 的 各 种 索 引 值 中 是 否 有 指 向 不 存 在 的 常 量 或 不 符 合 类 型 的 常 量。

·CONSTANT_Utf8_info 型 的 常 量 中 是 否 有 不 符 合 UTF-8 编 码 的 数 据。

·Class 文 件 中 各 个 部 分 及 文 件 本 身 是 否 有 被 删 除 的 或 附 加 的 其 他 信 息

1.2 元数据验证

第 二 阶 段 的 主 要 目 的 是 对 类 的 元 数 据 信 息 进 行 语 义 校 验, 保 证 不 存 在 与《 Java 语 言 规 范》 定 义 相 悖 的 元 数 据 信 息。

1.3 字节码验证

第 三 阶 段 是 整 个 验 证 过 程 中 最 复 杂 的 一 个 阶 段, 主 要 目 的 是 通 过 数 据 流 分 析 和 控 制 流 分 析, 确 定 程 序 语 义 是 合 法 的、 符 合 逻 辑 的。在 第 二 阶 段 对 元 数 据 信 息 中 的 数 据 类 型 校 验 完 毕 以 后, 这 阶 段 就 要 对 类 的 方 法 体( Class 文 件 中 的 Code 属 性) 进 行 校 验 分 析, 保 证 被 校 验 类 的 方 法 在 运 行 时 不 会 做 出 危 害 虚 拟 机 安 全 的 行 为。

1.4 符号引用验证

最 后 一 个 阶 段 的 校 验 行 为 发 生 在 虚 拟 机 将 符 号 引 用 转 化 为 直 接 引 用 的 时 候, 这 个 转 化 动 作 将 在 连 接 的 第 三 阶 段—— 解 析 阶 段 中 发 生。

符 号 引 用 验 证 的 主 要 目 的 是 确 保 解 析 行 为 能 正 常 执 行, 如 果 无 法 通 过 符 号 引 用 验 证, Java 虚 拟 机 将 会 抛 出 一 个 java.lang.IncompatibleClassChangeError 的 子 类 异 常, 典 型 的 如:java.lang.IllegalAccessError、 java.lang.NoSuchFieldError、 java.lang.NoSuchMethodError 等。

2.准备

准 备 阶 段 是 正 式 为 类 中 定 义 的 变 量( 即 静 态 变 量, 被 static 修 饰 的 变 量) 分 配 内 存 并 设 置 类 变 量 初 始 值 的 阶 段, 从 概 念 上 讲, 这 些 变 量 所 使 用 的 内 存 都 应 当 在 方 法 区 中 进 行 分 配, 但 必 须 注 意 到 方 法 区 本 身 是 一 个 逻 辑 上 的 区 域, 在 JDK 7 及 之 前, HotSpot 使 用 永 久 代 来 实 现 方 法 区 时, 实 现 是 完 全 符 合 这 种 逻 辑 概 念 的;而 在 JDK 8 及 之 后, 类 变 量 则 会 随 着 Class 对 象 一 起 存 放 在 Java 堆 中, 这 时 候“ 类 变 量 在 方 法 区” 就 完 全 是 一 种 对 逻 辑 概 念 的 表 述 了。

关 于 准 备 阶 段, 还 有 两 个 容 易 产 生 混 淆 的 概 念 笔 者 需 要 着 重 强 调。

首 先 是 这 时 候 进 行 内 存 分 配 的 仅 包 括 类 变 量, 而 不 包 括 实 例 变 量, 实 例 变 量 将 会 在 对 象 实 例 化 时 随 着 对 象 一 起 分 配 在 Java 堆 中。

其 次 是 这 里 所 说 的 初 始 值“ 通 常 情 况” 下 是 数 据 类 型 的 零 值, 假 设 一 个 类 变 量 的 定 义 为:public static int value = 123。那 变 量 value 在 准 备 阶 段 过 后 的 初 始 值 为 0 而 不 是 123, 因 为 这 时 尚 未 开 始 执 行 任 何 Java 方 法, 而 把 value 赋 值 为 123 的 putstatic 指 令 是 程 序 被 编 译 后, 存 放 于 类 构 造 器 < clinit >() 方 法 之 中, 所 以 把 value 赋 值 为 123 的 动 作 要 到 类 的 初 始 化 阶 段 才 会 被 执 行。

d069db459dd543b9c5dc995f22e78b77.png

上 面 提 到 在“ 通 常 情 况” 下 初 始 值 是 零 值, 那 言 外 之 意 是 相 对 的 会 有 某 些“ 特 殊 情 况”:如 果 类 字 段 的 字 段 属 性 表 中 存 在 ConstantValue 属 性, 那 在 准 备 阶 段 变 量 值 就 会 被 初 始 化 为 ConstantValue 属 性 所 指 定 的 初 始 值。

3.解析

解 析 阶 段 是 Java 虚 拟 机 将 常 量 池 内 的 符 号 引 用 替 换 为 直 接 引 用 的 过 程, 在 Class 文 件 中 它 以 CONSTANT_Class_info、 CONSTANT_Fieldref_info、 CONSTANT_Methodref_info 等 类 型 的 常 量 出 现。

符 号 引 用( Symbolic References):符 号 引 用 以 一 组 符 号 来 描 述 所 引 用 的 目 标, 符 号 可 以 是 任 何 形 式 的 字 面 量, 只 要 使 用 时 能 无 歧 义 地 定 位 到 目 标 即 可。符 号 引 用 与 虚 拟 机 实 现 的 内 存 布 局 无 关, 引 用 的 目 标 并 不 一 定 是 已 经 加 载 到 虚 拟 机 内 存 当 中 的 内 容。各 种 虚 拟 机 实 现 的 内 存 布 局 可 以 各 不 相 同, 但 是 它 们 能 接 受 的 符 号 引 用 必 须 都 是 一 致 的, 因 为 符 号 引 用 的 字 面 量 形 式 明 确 定 义 在《 Java 虚 拟 机 规 范》 的 Class 文 件 格 式 中。

直 接 引 用( Direct References):直 接 引 用 是 可 以 直 接 指 向 目 标 的 指 针、 相 对 偏 移 量 或 者 是 一 个 能 间 接 定 位 到 目 标 的 句 柄。直 接 引 用 是 和 虚 拟 机 实 现 的 内 存 布 局 直 接 相 关 的, 同 一 个 符 号 引 用 在 不 同 虚 拟 机 实 例 上 翻 译 出 来 的 直 接 引 用 一 般 不 会 相 同。如 果 有 了 直 接 引 用, 那 引 用 的 目 标 必 定 已 经 在 虚 拟 机 的 内 存 中 存 在。

关于符号引用和直接引用,可以参考https://www.zhihu.com/question/30300585?sort=created

《Java 虚 拟 机 规 范》 之 中 并 未 规 定 解 析 阶 段 发 生 的 具 体 时 间, 只 要 求 了 在 执 行 anewarray、checkcast、 getfield、 getstatic、 instanceof、 invokedynamic、 invokeinterface、 invoke-special、 invokestatic、 invokevirtual、 ldc、 ldc_w、 ldc2_w、 multianewarray、 new、 putfield 和 putstatic 这 17 个 用 于 操 作 符 号 引 用 的 字 节 码 指 令 之 前, 先 对 它 们 所 使 用 的 符 号 引 用 进 行 解 析。所 以 虚 拟 机 实 现 可 以 根 据 需 要 来 自 行 判 断, 到 底 是 在 类 被 加 载 器 加 载 时 就 对 常 量 池 中 的 符 号 引 用 进 行 解 析, 还 是 等 到 一 个 符 号 引 用 将 要 被 使 用 前 才 去 解 析 它。

对 同 一 个 符 号 引 用 进 行 多 次 解 析 请 求 是 很 常 见 的 事 情, 除 invokedynamic 指 令 以 外, 虚 拟 机 实 现 可 以 对 第 一 次 解 析 的 结 果 进 行 缓 存, 譬 如 在 运 行 时 直 接 引 用 常 量 池 中 的 记 录, 并 把 常 量 标 识 为 已 解 析 状 态, 从 而 避 免 解 析 动 作 重 复 进 行。

解 析 动 作 主 要 针 对 类 或 接 口、 字 段、 类 方 法、 接 口 方 法、 方 法 类 型、 方 法 句 柄 和 调 用 点 限 定 符 这 7 类 符 号 引 用 进 行, 分 别 对 应 于 常 量 池 的 CONSTANT_Class_info、 CON-STANT_Fieldref_info、 CONSTANT_Methodref_info、 CONSTANT_InterfaceMethodref_info、 CONSTANT_MethodType_info、CONSTANT_MethodType_info、 CONSTANT_MethodHandle_info、 CONSTANT_Dyna-mic_info 和 CONSTANT_InvokeDynamic_info 8 种 常 量 类 型。

4.初始化

加 载、 验 证、 准 备、 初 始 化 和 卸 载 这 五 个 阶 段 的 顺 序 是 确 定 的, 类 型 的 加 载 过 程 必 须 按 照 这 种 顺 序 按 部 就 班 地 开 始, 而 解 析 阶 段 则 不 一 定:它 在 某 些 情 况 下 可 以 在 初 始 化 阶 段 之 后 再 开 始, 这 是 为 了 支 持 Java 语 言 的 运 行 时 绑 定 特 性( 也 称 为 动 态 绑 定 或 晚 期 绑 定)。请 注 意, 这 里 笔 者 写 的 是 按 部 就 班 地“ 开 始”, 而 不 是 按 部 就 班 地“ 进 行” 或 按 部 就 班 地“ 完 成”, 强 调 这 点 是 因 为 这 些 阶 段 通 常 都 是 互 相 交 叉 地 混 合 进 行 的, 会 在 一 个 阶 段 执 行 的 过 程 中 调 用、 激 活 另 一 个 阶 段。

关 于 在 什 么 情 况 下 需 要 开 始 类 加 载 过 程 的 第 一 个 阶 段“ 加 载”,《 Java 虚 拟 机 规 范》 中 并 没 有 进 行 强 制 约 束, 这 点 可 以 交 给 虚 拟 机 的 具 体 实 现 来 自 由 把 握。但 是 对 于 初 始 化 阶 段,《 Java 虚 拟 机 规 范》 则 是 严 格 规 定 了 有 且 只 有 六 种 情 况 必 须 立 即 对 类 进 行“ 初 始 化”

4.1 初始化时机

4.1.1 四条字节码

遇 到 newgetstaticputstaticinvokestatic 这 四 条 字 节 码 指 令 时, 如 果 类 型 没 有 进 行 过 初 始 化, 则 需 要 先 触 发 其 初 始 化 阶 段。能 够 生 成 这 四 条 指 令 的 典 型 Java 代 码 场 景 有:

·使用new关键字实例化对象时·读取或设置一个类型的静态字段(被 final 修 饰、 已 在 编 译 期 把 结 果 放 入 常 量 池 的 静 态 字 段 除 外)·调用一个类型的静态方法
4.1.2 反射

使 用 java.lang.reflect 包 的 方 法 对 类 型 进 行 反 射 调 用 的 时 候, 如 果 类 型 没 有 进 行 过 初 始 化, 则 需 要 先 触 发 其 初 始 化。

4.1.3 初始化父类

当 初 始 化 类 的 时 候, 如 果 发 现 其 父 类 还 没 有 进 行 过 初 始 化, 则 需 要 先 触 发 其 父 类 的 初 始 化。

4.1.4 main

当 虚 拟 机 启 动 时, 用 户 需 要 指 定 一 个 要 执 行 的 主 类( 包 含 main() 方 法 的 那 个 类), 虚 拟 机 会 先 初 始 化 这 个 主 类。

4.1.5 动态语言

当 使 用 JDK 7 新 加 入 的 动 态 语 言 支 持 时, 如 果 一 个 java.lang.invoke.MethodHandle 实 例 最 后 的 解 析 结 果 为 REF_getStatic、 REF_putStatic、 REF_invokeStatic、 REF_newInvokeSpecial 四 种 类 型 的 方 法 句 柄, 并 且 这 个 方 法 句 柄 对 应 的 类 没 有 进 行 过 初 始 化, 则 需 要 先 触 发 其 初 始 化。

4.1.6 接口default方法

当 一 个 接 口 中 定 义 了 JDK 8 新 加 入 的 默 认 方 法( 被 default 关 键 字 修 饰 的 接 口 方 法) 时, 如 果 有 这 个 接 口 的 实 现 类 发 生 了 初 始 化, 那 该 接 口 要 在 其 之 前 被 初 始 化。

4.2 初始化流程

进 行 准 备 阶 段 时, 变 量 已 经 赋 过 一 次 系 统 要 求 的 初 始 零 值, 而 在 初 始 化 阶 段, 则 会 根 据 程 序 员 通 过 程 序 编 码 制 定 的 主 观 计 划 去 初 始 化 类 变 量 和 其 他 资 源。我 们 也 可 以 从 另 外 一 种 更 直 接 的 形 式 来 表 达:初 始 化 阶 段 就 是 执 行 类 构 造 器 < clinit >() 方 法 的 过 程。< clinit >() 并 不 是 程 序 员 在 Java 代 码 中 直 接 编 写 的 方 法, 它 是 Javac 编 译 器 的 自 动 生 成 物, 但 我 们 非 常 有 必 要 了 解 这 个 方 法 具 体 是 如 何 产 生 的, 以 及 < clinit >() 方 法 执 行 过 程 中 各 种 可 能 会 影 响 程 序 运 行 行 为 的 细 节, 这 部 分 比 起 其 他 类 加 载 过 程 更 贴 近 于 普 通 的 程 序 开 发 人 员 的 实 际 工 作。

< clinit >() 方 法 是 由 编 译 器 自 动 收 集 类 中 的 所 有 类 变 量 的 赋 值 动 作 和 静 态 语 句 块( static{} 块) 中 的 语 句 合 并 产 生 的, 编 译 器 收 集 的 顺 序 是 由 语 句 在 源 文 件 中 出 现 的 顺 序 决 定 的, 静 态 语 句 块 中 只 能 访 问 到 定 义 在 静 态 语 句 块 之 前 的 变 量, 定 义 在 它 之 后 的 变 量, 在 前 面 的 静 态 语 句 块 可 以 赋 值, 但 是 不 能 访 问。

< clinit >() 方 法 与 类 的 构 造 函 数( 即 在 虚 拟 机 视 角 中 的 实 例 构 造 器 < init >() 方 法) 不 同, 它 不 需 要 显 式 地 调 用 父 类 构 造 器, Java 虚 拟 机 会 保 证 在 子 类 的 < clinit >() 方 法 执 行 前, 父 类 的 < clinit >() 方 法 已 经 执 行 完 毕。因 此 在 Java 虚 拟 机 中 第 一 个 被 执 行 的 < clinit >() 方 法 的 类 型 肯 定 是 java.lang.Object。由 于 父 类 的 < clinit >() 方 法 先 执 行, 也 就 意 味 着 父 类 中 定 义 的 静 态 语 句 块 要 优 先 于 子 类 的 变 量 赋 值 操 作。

< clinit >() 方 法 对 于 类 或 接 口 来 说 并 不 是 必 需 的, 如 果 一 个 类 中 没 有 静 态 语 句 块, 也 没 有 对 变 量 的 赋 值 操 作, 那 么 编 译 器 可 以 不 为 这 个 类 生 成 < clinit >() 方 法。接 口 中 不 能 使 用 静 态 语 句 块, 但 仍 然 有 变 量 初 始 化 的 赋 值 操 作, 因 此 接 口 与 类 一 样 都 会 生 成 < clinit >() 方 法。但 接 口 与 类 不 同 的 是, 执 行 接 口 的 < clinit >() 方 法 不 需 要 先 执 行 父 接 口 的 < clinit >() 方 法, 因 为 只 有 当 父 接 口 中 定 义 的 变 量 被 使 用 时, 父 接 口 才 会 被 初 始 化。此 外, 接 口 的 实 现 类 在 初 始 化 时 也 一 样 不 会 执 行 接 口 的 < clinit >() 方 法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值