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

JVM 类加载

JVM 类加载

一. 类文件结构

一个简单的 HelloWorld.java

package cn.itcast.jvm.t5;
​
// HelloWorld 示例
public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");}
}

执行 javac -parameters -d . HelloWorld.java 就能得到字节码文件. (其中 -parameters 表示"让编译器在生成的字节码中记录方法参数的名称信息").

1. 类文件结构规范
ClassFile {u4              magic;                                   # 魔数 (标识文件是否为有效的类文件)u2              minor_version;                           # 次版本号u2              major_version;                           # 主版本号   u2              constant_pool_count;                     # 常量池长度cp_info         constant_pool[constant_pool_count-1];    # 常量池u2              access_flags;                            # 访问标志 (public/private/final/abstract)u2              this_class;                              # 当前类u2              super_class;                             # 父类u2              interfaces_count;                        # 接口数量u2              interfaces[interfaces_count];            # 接口 u2              fields_count;                            # 字段数量field_info      fields[fields_count];                    # 字段u2              methods_count;                           # 方法数量method_info     methods[methods_count];                  # 方法u2              attributes_count;                        # 类的附加属性数量attribute_info  attributes[attributes_count];            # 类的附加属性
}
2. 部分字段说明
  1. 魔数

0~3字节 (共占4个字节) 表示当前文件是否为 class 类型的文件 (不同类型的文件有不同的魔数信息).

  • ca fe ba be 表示当前文件时 .class 文件.

  1. 版本

4~7字节 (共占4个字节) 表示类的版本.

  • 00 00 00 34 (十进制52) 表示 Java8.

例子: 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

  1. 常量池

Constant TypeValue
CONSTANT_Class7
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_String8
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_NameAndType12
CONSTANT_Utf81
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18

8~9字节 (共占4个字节) 表示常量池长度. 例如: 00 23 (十进制35) 表示常量池中有34项 (#1~#34项).

从10字节开始往后就是常量池.

字节码文件示例:

ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 
00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07 
00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 
56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 
75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 
61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 
00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63 
61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f 
57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 
28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 
69 6e 67 3b 29 56 01 00 04 69 72 67 73 01 00 13 
5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 
6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61 
6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46 
69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64 
2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e 
00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 
07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74 
63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 
6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61 
6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61 
2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f 
75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 
69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76 
61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 
01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a 
61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 
29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01 
00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01 
00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00 
00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00 
01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00 
0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00 
09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a 
00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b 
00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00 
00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00 
00 00 02 00 14

二. 类加载阶段

1. 加载
(1) 核心任务

将类的字节码文件 (.class文件) 加载到 JVM 内存中, 并生成一个代表该类的 java.lang.Class 对象.

(2) 具体操作
  • 通过类的全限定名 (如com.example.User) 找到对应的字节码文件.

  • 将字节码文件的二进制数据读入 JVM 内存.

  • 在方法区中创建该类的运行时数据结构 (包含 类的版本, 字段, 方法等信息).

  • 在堆中生成一个 Class 类的实例, 作为方法区中该类数据的访问入口.

注意

  • 加载阶段由类加载器 (ClassLoader) 完成.

  • 加载阶段与连接阶段的部分操作可能重叠 (如字节码验证可能在加载未完成时就开始).

image-20250911191333127

2. 连接
2.1 验证

验证字节码是否符合 JVM 规范, 避免不合规范的字节码危害 JVM 安全.

主要验证内容:

  1. 文件格式验证 (检查是否符合类文件规范 如 魔数, 版本号 是否正确).

  2. 元数据验证 (检查类的元数据信息 如 访问修饰符, 类中字段和方法 是否符合语法规则).

  3. 字节码验证 (检查方法体中的字节码指令是否合法).

  4. 符号引用验证 (检查常量池中的符号是否能被正确解析).

2.2 准备

为类的 静态变量 (类变量) 分配内存, 并设置默认初始值.

:

  • 默认初始值并非代码中定义的初始值, 而是 Java 的 "零值" (如 int 为 0, boolean 为 false, 引用类型为 null).

  • 被 final 修饰的 基本类型 或 字符串类型 赋值操作会在准备阶段完成.

2.3 解析

将常量池中的 符号引用 转换为 直接引用.

  • 符号引用: 用字符串描述的引用 (如类名, 方法名, 字段名), 存在于字节码的常量池中 (在编译期生成).

  • 直接引用: 指向内存中实际地址的引用 .

解析阶段并非必须按顺序执行, JVM 可在执行字节码指令时动态触发解析 (延迟解析).

3. 初始化
(1) 核心任务

执行类的初始化代码 (执行 静态变量赋值 和 静态代码块), 将类变量设置为开发者定义的初始值.

(2) 触发时机

初始化阶段是类加载的最后一步, 只有在 主动使用类 时才会触发初始化 (是惰性的).

常见触发时机如下:

  • 创建类的实例 (如 new User()).

  • 调用类的静态方法.

  • 访问类的静态变量 (非final).

  • 反射调用 (如 Class.forName("com.example.User") )

  • 初始化子类时, 其父类会先被初始化.

(3) 执行顺序

  • 父类的初始化代码先于子类执行.

  • 静态变量的赋值与静态代码块按代码定义顺序执行.

注: 不会触发初始化的情况

  • 访问类的 static final 静态常量 (基本类型和字符串) 不会触发初始化.

  • 访问类对象 .class 不会触发初始化.

  • 创建该类的数组不会触发初始化.

  • 类加载器的 loadClass() 方法.

  • Class.forName 的参数 2 为 false 时.

三. 类加载器

以 JDK 8 为例, 类加载器的层级关系如下:

名称加载哪里的类说明
Bootstrap ClassLoader (启动类加载器)JAVA_HOME/jre/lib无法直接访问
Extension ClassLoader (扩展类加载器)JAVA_HOME/jre/lib/ext上级为 Bootstrap, 显示为 null
Application ClassLoader (应用程序类加载器)classpath上级为 Extension
自定义类加载器自定义上级为 Application

1. 启动类加载器

("Bootstrap ClassLoader")

是 Java 类加载层次结构中的顶层类加载器

  • 加载范围: 负责加载 Java 运行时核心类库, 主要加载 %JAVA_HOME%/jre/lib 目录下的类库文件 (如 java.lang.Object, java.util.List 等). 这些类库是 Java 运行的基础, 提供了最核心的功能.

  • 实现方式: 由 C++ 语言实现, 是JVM的一部分.

    在 Java 程序中无法直接访问. 如果在 Java 代码中调用某个类的getClassLoader()方法, 得到的返回值为 null, 就说明这个类是由启动类加载器加载的.

  • 作用: 加载 Java 核心类库, 为 Java 程序提供最基础的类加载支持, 保证 JVM 能够运行最基本的 Java 代码.

2. 扩展类加载器

("Extension ClassLoader")

扩展类加载器 的父加载器是 启动类加载器

  • 加载范围: 负责加载 Java 的扩展类库, 主要加载 %JAVA_HOME%/jre/lib/ext 目录下 和 由 java.ext.dirs 系统属性指定的目录中的类库文件. 这些扩展类库可以用于扩展 Java 的功能, 例如一些第三方提供的与系统交互相关的扩展包等.

  • 实现方式: 由 Java 语言实现, 是 java.lang.ClassLoader类的子类.

    如果在 Java 代码中调用某个类的getClassLoader()方法, 得到的返回值为 扩展类加载器的实例, 就说明这个类是由扩展类加载器加载的.

  • 作用: 加载扩展目录下的类库, 为 Java 程序提供额外的功能支持, 且加载的类可以被应用程序类加载器所加载的类访问.

3. 应用程序类加载器

("Application ClassLoader")

应用程序类加载器 的父加载器是 扩展类加载器.

  • 加载范围: 负责加载 应用程序的类路径 (classpath) 下的所有类, 包括开发者自己编写的类以及引用的第三方类库 (比如通过Maven / Gradle 引入的依赖). 开发中, 我们编写的业务代码, 配置文件等都是由应用程序类加载器来加载的.

  • 实现方式: 由 Java 语言实现, 是java.lang.ClassLoader 类的子类.

    如果在 Java 代码中调用某个类的getClassLoader()方法, 得到的返回值为 应用程序类加载器的实例, 就说明这个类是由应用程序类加载器加载的.

  • 作用: 是 Java 应用程序中最常用的类加载器, 负责加载应用程序运行所需的各类业务逻辑类依赖类库, 让应用程序能够正常运行.

4.双亲委派模型

这三种类加载器遵循 双亲委派模型: 即当一个类加载器收到类加载请求时, 它 不会尝试自己去先加载这个类,而是把请求委托给父类加载器去完成, 依次向上, 直到顶层的启动类加载器. 只有当父类加载器无法完成加载任务时 (即: 父类加载器在它的加载范围内找不到需要加载的类), 子类加载器才会尝试自己去加载这个类.

双亲委派模型的优点:

  1. 避免类的重复加载: 比如 java.lang.Object 类, 无论由哪个类加载器加载, 最终都是由启动类加载器加载, 不会出现多个不同版本的 Object 类.

  2. 保证 Java 核心类库的安全性: 核心类库都是由启动类加载器加载, 不可被篡改和替换. 防止了恶意代码替换 Java 核心类造成安全问题.

http://www.dtcms.com/a/411679.html

相关文章:

  • golang如何四舍五入到2位小数
  • ps临摹图片做网站的图片犯法吗黄冈贴吧
  • 天津智能网站建设哪里有北京太阳宫网站建设
  • 深圳网站建设的费用宁波公司名称大全
  • 国家建设部网站wordpress最新中文版
  • 商河大模型-网易智企发布的客服领域行业大模型
  • 国家电力安全网站两学一做企业电子商务网站平台建设
  • 网站建设软件公司唯爱wordpress主题
  • 用 PyTorch 搞定 CIFAR10 数据集
  • VLMs距离空间智能还有多远的路要走?
  • 做网站北京德国网站建设
  • 网站建立安全连接失败软装设计公司加盟
  • 搭建个人博客:云服务器IP如何使用
  • iis网站asp.net部署网站建设运营费计入什么科目
  • 建设外贸营销型网站需要什么青岛网站设计定制
  • 券商 做网站圣都装饰的口碑怎么样
  • 【算法训练营Day26】动态规划part2
  • 河北衡水建设网站公司电话wordpress ajax登录插件
  • 网站源码怎么搭建最新新闻热点事件2023年10月
  • 城乡建设部网站广州市国外学校网站设计
  • 泊头网站建设公司wordpress删除主题之后
  • 一站式营销平台wordpress学校网站模板
  • LeetCode 算法题【简单】338. 比特位计数
  • 买房网站排名福州做网站建设公司
  • 爱思强交付第100套G10-SiC系统
  • 网站的建设要多少钱求推荐专门做借条的网站
  • 在线旅游攻略网站建设方案做网站要注册第35类商标吗
  • RocketMQ 核心知识整理:工作原理、常用命令与常见问题解决
  • 做养生网站怎么赚钱麻涌建设网站
  • 域名备案 没有网站网站建设意见建议表