深入解读Java类加载过程
深入解读Java类加载过程
1.加载
1.通过全类限定名获取此类的二进制字节流。
2.将此字节流所代表的静态存储结构转化为方法区中的运行时数据结构。
典型的运行时数据结构:
· Class对象:代表类本身的对象,保存了类的元数据(如字段、方法信息、常量池等)
· 方法表和字段表:存储方法和字段的实际运行时信息
· 常量池:已经在方法区加载并解析的常量池数据
代码语言:java复制public class Example {
private int number;
public void printumber() {
println(number);
}
}
字节码:当我们编译这个 Java 类时,编译器会生成一个 文件,里面包含了 Example 类的字节码。
字节流转化:
JVM 加载这个 .class 文件,读取其字节流。
字节流中包含了类名、字段 number、方法 printumber() 以及常量池(例如:println)。
转换为运行时数据结构:
JVM 解析字节流,将其转化为方法区中的 Class 对象,代表这个类的元数据。
类的字段(number)和方法(printumber())会被转化为 字段表 和 方法表,供后续操作。
类中的常量池数据也会被解析并存储到方法区。
存储到方法区:解析后的类信息被存储在方法区中的相应位置,JVM 就可以通过 Class 对象访问类的各种信息,如字段、方法、常量等。
.在内存中生产一个代表此类的Class对象,作为方法区这个类各种数据的访问入口。
第一点可以非常灵活,Class文件可以从ZIP压缩包中读取——JAR,WAR的基础。从网络中获取。运行时动态计算生成。
从加密文件获取
额外讲一下数组与非数组对象在加载中的区别。
数组对象不通过Class文件生成,由JVM自动生生成。若是引用类型如String[]则通过类加载器加载元素类。若是基本数据类型int[],内置于JVM中,在JVM运行时生成。而非数组类则通过ClassLoader处理。
内存分配的区别
普通对象的内存分配通过JVM的堆内存进行,JVM在堆上为对象分配内存,并将对象引用保存在栈上或其他对象字段中
数组对象同样在堆中分配内存,多出了一个数字长度字段且在空间上是连续的,有利于快速访问。
2.连接
1.验证
目的:确保Class文件中的内容符合《Java虚拟机规范》,这些代码运行后不会危机虚拟机自身的安危。
1.文件格式验证
如是否以魔数开头,主次版本号是否被接收,指向常量池的索引值中是否会指向不存在的常量或不符合类型的常量。类的字段,方法是否符合.class文件的语法规范。。。。
2.元数据验证
对字节码进行语义分析如这个类是否有父类除(Object),有无继承final修饰的类,非抽象类是否实现了父类或接口中要求实现的方法。类和接口的定义是否符合规范。方法签名是否一致,特别是方法参数和返回值的类型验证。检查类的访问权限。。。
.字节码验证
重要概念StackMapTable,JDK6后新增存在于Code属性中(不懂可翻看我之前写的Java类文件结构)
代码语言:tex复制StackMapTable_attribute {
u2 attribute_name_index;// 属性名称索引,指向常量池中的属性名("StackMapTable")。
u4 attribute_length;// 属性长度。
u2 number_of_entries; // 栈映射表中条目的数量。
stack_map_frame entries[number_of_entries];// 一个条目数组,每个条目描述了一个栈帧
}
stack_map_frame表示一个栈帧,它有多个变体,常见的栈帧类型包括:
SameFrame:当前帧与前一个帧的局部变量表和操作数栈相同。
SameLocals1StackItemFrame:局部变量表未变化,但操作数栈有一个新的项。
ChopFrame:局部变量表缩短了一些项。
AppendFrame:局部变量表增加了一些项。
它记录了方法执行时,在特定字节码指令的栈帧位置,以便JVM可以快速验证字节码的正确性。
验证原理:在字节码的执行过程中,虚拟机会根据字节码操作调整栈帧的状态,例如iload会从局部变量表加载一个int值并压入操作数栈;iadd操作会从操作数栈弹出两个int值并执行加法运算。
在这其中StackMapTable保证不会将int值当作float来操作,不能将null引用当作对象来操作,确保局部变量一致性,对其进行类型检查。
检查字节码中是否存在栈的溢出,未平衡的栈操作(如调用pop弹出栈中的数据,但栈为空无法满足操作要求,或向栈中推入数据,但是栈空间已满)。通过它,字节码验证器可以跳过详细的计算,直接从中获取栈帧信息,确保操作正确。大大优化了验证流程。
我们打破沙锅问到底。所以StackMapTable中的数据是从哪里来的?什么时候生成的?
其实猜也能猜出来,毕竟现在从一个java文件到真正运行起来也就执行了几步,距离真正运行还差很远。我们的编译器在生成字节码文件时,会进行控制流分析,确定代码执行路径。如if-else,循环,异常处理,方法调用。
1.基本块分割:把程序分为一段段不包含跳转的连续代码
2.计算栈状态:编译器跟踪每个基本块的入口栈帧状态。如进入某基本块后,记录操作数栈中有那些类型的值,局部变量表存储了那些类型的数据。
.记录关键位置关键位置栈帧状态:将第二点计算出来的值保存。包括不限于方法调用入口点,异常处理开始位置,跳转指令的目标位置。这些东西就是StackMapStack中的Frame
4.符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候。这个动作发生在解析连接的第三阶段——解析阶段。符号引用包括:类符号java/lang/Object,字段符号name:Ljava/lang/string;表示字段name类型时string,方法符号methodame(I)V表示一个方法methodame参数类型为Int,返回类型为Void
验证能否通过字符串描述的全限定名到对应的类。在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段 。符号引用中的类,方法,字段的可访性(privaet/protect/public/<package>)是否可被当前类访问。
常见的验证失败:类不存在:符号引用指向的类在类路径中无法到,导致ClassotFoundException
。字段不存在::符号引用中的字段名称或字段描述符不匹配,例如符号引用指向一个int
类型的字段,而类中实际字段类型是String
,则会抛oSuchFieldError
。
方法不存在:符号引用中的方法名称或方法描述符不匹配,例如符号引用描述的是methodame(int)
方法,但类中实际方法签名是methodame(String)
,则会抛出oSuchMethodError
验证阶段对于虚拟机类加载机制来说非常重要,但却不是一个必须要执行的阶段。哈哈哈懵了吧。
因为这一验证阶段只有通过与不通过的差别,只要通过了验证,其后就对程序没有任何影响了。如果程序运行的代码已经被反复使用和验证过,那么在生产环境可以考虑使用-Xvertify:none参数来关闭此验证,缩短虚拟机类加载时间。
.准备
将静态变量(static)分配内存并设置类变量初始值。设0,设null。如果被final修饰,在编译时就会直接赋值。
4.解析
将常量池中的符号引用转化为直接引用的过程。
除了动态invokedynamic指令外,其他的指令在进行符号进行解析请求时,保证如果一个符号引用已经被成功解析过来,那么后面的请求同样要成功,而如果失败了那其他解析请求同样失败。虚拟机会将第一次解析结果进行缓存。
1.类或接口的解析 2.字段解析 .方法解析 4.接口方法解析
5.初始化
类加载的最后一个阶段。直接来说就是执行类构造器<clinit>方法()的过程。他是javac编译器自动生成的,收集了类中所有类变量的复制动作和静态语句。JVM保证子类的<clinit>执行器父类的<clinit>执行完毕。它不是必要的,如果一个类没有静态语句块和对变量的复制操作,可以不生成。
#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
推荐阅读
留言与评论(共有 12 条评论) |
本站网友 东亚望京 | 14分钟前 发表 |
但操作数栈有一个新的项 | |
本站网友 追风膏 | 1分钟前 发表 |
如果程序运行的代码已经被反复使用和验证过 | |
本站网友 北京军区医院 | 7分钟前 发表 |
类和接口的定义是否符合规范 | |
本站网友 南京信息职业技术学院地址 | 23分钟前 发表 |
符号引用包括:类符号java/lang/Object | |
本站网友 南宫二手房 | 5分钟前 发表 |
包括不限于方法调用入口点 | |
本站网友 唯亭二手房 | 23分钟前 发表 |
字段 number | |
本站网友 androidsdk | 16分钟前 发表 |
本站网友 下南洋 | 10分钟前 发表 |
本站网友 黑水公司 | 7分钟前 发表 |
但是栈空间已满) | |
本站网友 html个人主页模板 | 27分钟前 发表 |
大大优化了验证流程 | |
本站网友 醉后放手 | 0秒前 发表 |
符号引用中的类 |