文章目录
- 重点:
- 1.类加载的三个过程-简述
- 2. 类的主动使用和被动使用
- 主动使用的场景-new,反射,使用静态部分,子导致父初始化
- 被动使用
- 3. 类的加载过程详解
- 3.1 类的加载阶段
- 3.2 类的连接阶段
- 验证
- 元数据:确保class符合jvm规范。语义验证。
- 准备
- 解析
- 3.3 初始化
- 类加载过程实例剖析
- 补充例子
重点:
- 连接-准备阶段为静态变量赋初值,初始化阶段为静态变量赋代码值
- 引起初始化阶段的6种情况(主动引用),静态变量/方法,new,反射,子类引起父类。
- 被动引用,如静态常量。其值copy到被引用的类。
- 类加载的三个阶段
- 静态代码块可以对后面的静态变量赋值,但不能访问。
- clinit线程安全
1.类加载的三个过程-简述
- 加载阶段
查找并加载class文件
loadClass from somewhere -> getClassBytes and defineClass->return Class
常见异常:ClassNotFoundException,NoClassDefFoundError - 连接阶段
a. 验证
确保类文件正确性。如class版本,魔数 CAFEBABE
b. 准备-赋初始值
为类的静态变量 分配内存,初始化【默认值】。
c. 解析
将类中符号引用转为直接引用。
如引用了其他类, 则其他类先要经历类的所有加载过程。 - 初始化阶段
<clinit>()
-赋代码值
class initailze为类的静态变量赋予正确的值(代码编写时指定的值)
2. 类的主动使用和被动使用
JVM虚拟机规定,类和接口被java程序首次主动使用才会对其进行初始化。
主动使用的场景-new,反射,使用静态部分,子导致父初始化
- new
- 访问类的静态变量
- 访问类的静态方法
- 对类进行反射操作 。 如 Class.forName(“xx”); 参考java.sql.driver初始化驱动时调用DriverManager注册driver。ps:ClassLoader.loadClass不会触发【连接-初始化阶段】,只会触发【加载阶段】。
- 初始化子类导致父类的初始化
同理, 调用子类的静态变量会导致父类的初始化。
6. 启动类:执行main锁在的类会导致该类的初始化
除了上述6种,其他的都为被动使用,不会导致类的【加载】和【初始化】。
被动使用
- 引用类的静态常量
ps:静态常量会在编译期间被计算出来copy到其他类,可使用javap查看class文件 - 声明数组
3. 类的加载过程详解
例子
3.1 类的加载阶段
关键:1.得到class对应的二进制流(不限方式)2.defineClass
类的加载最终产物是堆内存中的class对象,对于同一个classLoader,不管加载多少次,对应到堆内存中的class是同一个(from cache)。
所以可以使用不同的classLoader加载同名的不同类
虚拟机规范规定类的加载是通过全限定名(包名+类名)来获取二进制数据流。
调用loadClass->findClass->getClassBytes+defineClass
常见的是class二进制文件形式,但可以是以下:
- 运行时动态生成,如ASM ,jdk动态代理,CGLIB
- 网络获取
- 读取zip获得类的二进制字节流,如war,jar。
war,jar使用的是和zip一样的压缩算法。 - 将类的二进制文件储存在数据库的BLOB字段类型中。
- 运行时生成class文件,并动态加载,如Thrift/AVRO可以在运行时将schema文件生成对应的class文件再加载。
3.2 类的连接阶段
验证
文件格式:确保类文件正确性。如class版本,魔数 CAFEBABE,MD5比对(class末尾)等。
元数据:确保class符合jvm规范。语义验证。
字节码:…
符号引用:…
准备
为类的静态变量 分配内存,初始化默认值。
如 static int a = 10; 在【准备阶段】a是0而不是10。
如final static int b = 10; 注意,final static变量是【被动引用】,在编译期间即直接赋予了10;
解析
将类中符号引用转为直接引用。
在常量池中寻找类、接口、字段、方法的符号引用,将其替换成直接引用。
3.3 初始化
()保证顺序,对后面的静态变量只能赋值不能访问
类加载过程实例剖析
补充例子
发布于2019年7月14日 20:24:54