类加载的过程
加载
- 将类的.class文件中的二进制数据读入到内存中
- 将其放在运行时数据区的方法区内
- 然后再内存中创建一个java.lang.Class对象用来封装类在方法区内的数据结构
简单地说,加载:在硬盘上查找并通过IO读入字节码文件
验证
目的:确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
主要分为四个检验部分:
- 文件格式验证:检验是否为class文件
- 元数据验证:验证是否符合Java语言规范
- 字节码验证:检验程序是否合法,符合逻辑
- 符号引用验证:保证该引用能够被访问到
要注意,验证阶段是非常重要的,但并不是一定必要的,对于反复使用和验证过的代码,可以通过使用-Xverify:none来关闭大部分类验证,来缩短验证时间。
准备
为类变量分配内存,设置类变量初始值
要注意的是:
- 内存分配仅包括类变量(static),不包括实例变量,实例变量会在对象实例化时随对象分配在Java堆中
- 初始值在通常情况下是0,并不是程序里的那个值。真正的赋值是在初始化时完成的。
解析
将符号引用替换为直接引用,该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接的过程(程序加载期间完成),动态链接时在程序运行期间完成的符号引用替换为直接引用。
符号引用:用符号来表示目标,符号可以是任意的字面量,要求无歧义
我们以字节码文档为例:
将一个简单的代码用javap命令生成字节码文件,内容如下
package Test;
public class Test {
public int computer(){
int a=1;
int b=2;
return a+b;
}
public static void main(String[] args) {
Test test = new Test();
int c = test.computer();
System.out.println(c);
}
}
```
Classfile /C:/Users/董润泽/workspace/Java虚拟机/src/Test/Test.class
Last modified 2020-1-21; size 490 bytes
MD5 checksum 1015aa0bd5fd4af85cc23a21ef42a1af
Compiled from "Test.java"
public class Test.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // Test/Test
#3 = Methodref #2.#18 // Test/Test."<init>":()V
#4 = Methodref #2.#20 // Test/Test.computer:()I
#5 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #23.#24 // java/io/PrintStream.println:(I)V
#7 = Class #25 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 computer
#13 = Utf8 ()I
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 SourceFile
#17 = Utf8 Test.java
#18 = NameAndType #8:#9 // "<init>":()V
#19 = Utf8 Test/Test
#20 = NameAndType #12:#13 // computer:()I
#21 = Class #26 // java/lang/System
#22 = NameAndType #27:#28 // out:Ljava/io/PrintStream;
#23 = Class #29 // java/io/PrintStream
#24 = NameAndType #30:#31 // println:(I)V
#25 = Utf8 java/lang/Object
#26 = Utf8 java/lang/System
#27 = Utf8 out
#28 = Utf8 Ljava/io/PrintStream;
#29 = Utf8 java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (I)V
{
public Test.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public int computer();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: ireturn
LineNumberTable:
line 5: 0
line 6: 2
line 7: 4
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class Test/Test
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method computer:()I
12: istore_2
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
20: return
LineNumberTable:
line 10: 0
line 11: 8
line 12: 13
line 13: 20
}
SourceFile: "Test.java"
可以看到,上面有一些“#数字”:
这其实就是生成的符号,对于后面的解释,例如“#7:#8”即指“#1”这个符号引用了“#7”和“#8”的内容
这即为符号引用
直接引用:引用的是目标的一个地址
初始化
对类的静态变量初始化为指定的值,执行静态代码块
同准备阶段的赋初始值不同!
在博客 类加载的时机已介绍,可参考理解。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2470290795@qq.com
文章标题:类加载的过程
文章字数:1.1k
本文作者:runze
发布时间:2020-01-28, 16:46:14
最后更新:2020-01-28, 19:26:41
原始链接:http://yoursite.com/2020/01/28/JVM/JVM%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B62%E2%80%94%E2%80%94%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E8%BF%87%E7%A8%8B/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。