1.加载
(1)通过类的全限定名来获取此类的二进制字节流
(2)将此字节流代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口
2.验证
(1)文件格式验证
:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
是否以魔数0xCAFEBABE开头
主次版本号是否在当前处理机处理范围之内
常量池的常量中是否有不被支持的常量类型(检查常量tag标志)
指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
CONSTANT_Utf8_info类型的常量中是否有不符合UTF8编码的数据
Class文件中各部分及文件本身是否有被删除或附加的其他信息
……
(2)元数据验证
:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
这个类是否有父类(除了Object之外,所有类都应当有父类)
这个类的父类是否继承了不允许被继承的类(被final修饰的类)
如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法
类中的字段、方法是否与父类产生了矛盾(覆盖父类的final字段,不符合规则的方法重载)
……
(3)字节码验证
:主要工作是进行数据流和控制流分析,保证被检验的类不会做出危害虚拟机的行为
保证任意时刻操作数栈的数据类型与指令代码顺序都能配合工作
保证跳转指令不会跳转到方法体之外的字节码指令上
保证方法体的类型转换是有效的
……
(4)符号引用验证
:在解析阶段中发生。
符号引用验证可以看作是对类自身以外(常量池中各种符号引用)的信息进行匹配性的校验,通常需要校验以下内容:
符号引用中通过字符串描述的全限定名是否能找到对应的类
在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问
……
可通过-Xverify:none来关闭大部分的类验证措施,以便缩短虚拟机类加载的时间
3.准备
准备阶段正式为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
这里需要注意的是,不对实例变量分配内存,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中。
比如:public static int value = 123;
这里所指的初始值是给value赋0(boolean是false,reference是null),而123是要在类初始化(clinit)中赋的。
4.解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用以CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info等类型的常量出现。
符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。
直接引用(Direct Reference):直接引用可以是指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,
同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info
四种常量类型。
(1)类或接口的解析
假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,那虚拟机完成整个解析过程需要包括以下三个步骤:
1)如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器其加载这个类C。在加载过程中,由于元数据验证、字节码验证的需要,又将可能触发其他相关类的加载动作(父类或接口)。
2)如果C是一个数组类型,并且数组的原属类型为对象,也就是N的描述符会是类型"[Ljava.lang.Integer"的形式,那将会按照第一点的规则加载数组元素类型。
如果N的描述符如前面所假设的形式,需要加载的元素类型就是"java.lang.Integer",接着由虚拟机生成一个代表此数组维度和元素的数组对象。
3)如果上面的步骤没有出现任何异常,那么C在虚拟机中实际上已经成为一个有效的类或者接口了,但在解析完成之前还要进行符号引用验证,确认C是否具备对D的访问权限。
(2)字段解析
要解析一个未被解析过的字段符号引用,首先将会对字段表内的class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是字段所属的类或者接口的符号引用。
如果解析完成,那将这个字段所属的类或接口用C表示,虚拟机规范要求按照如下步骤进行后续字段的搜索:
1)如果C本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
2)否则,如果在C中实现了接口,将会按照继承关系从上到下递归搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
3)否则,如果C不是Object的话,将会按照继承关系从上到下递归搜索其父类,如果在父类中包含了简单名称和字段描述符都与目标匹配的字段,则返回此字段,查找结束。
4)否则,查找失败,抛出NoSuchFieldError
如果成功找到该字段,将会对这个字段进行权限验证,如果发现不具备对字段的访问权限,将抛出IllegalAccessError
(3)类方法解析
类方法解析的第一个步骤与字段解析一样,也要先解析出类方法表的class_index项中索引的方法所属的类或接口的符号引用,如果解析成功,我们依然用C表示这个类,接下来虚拟机将会按照如下步骤进行后续的类方法搜索:
1)类方法和接口方法符号引用的常量类型定义是分开的,如果在类方法表中发现class_index中索引C是个接口,那么直接抛出IncompatibleClassChangeError
2)如果通过了第1)步,在类C中查找是否有简单名和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
3)否则,在类C的父类中递归查找是否有简单名和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
4)否则,在类C实现的接口列表及它们的父接口之中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果存在匹配的方法,说明C是一个抽象类,这时候查找结束,抛出AbstractMethodError
5)否则,宣告查找失败,抛出NoSuchMethodError
如果成功找到了该方法,并返回直接引用,将会对这个方法进行权限验证,如果发现不具备对此方法的访问权限,将抛出IllegalAccessError
(4)接口方法解析
接口方法也是需要先解析出接口方发表的class_index项中索引的方法所属的类或者接口的符号引用,如果解析成功,依然用C表示这个接口,接下来虚拟机将按照如下的步骤进行后续方法搜索:
1)与类方法相反,如果在接口方法中表中发现class_index中的索引C是一个类而不是接口,那就直接抛出IncompatibleClassChangeError
2)否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
3)否则,在接口C的父接口中递归查找,直到Object(查找范围会包括Object类)位置,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
4)否则,宣告方法查找结束,抛出NoSuchMethodError
由于接口中所有的方法都默认是public的,所以不存在访问权限问题
5.初始化
本过程在有任何调用本类的(参数或者方法)前提下才进行,如果仅仅是loadclass,不触发
类初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java代码。
在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者从另一个角度来说:初始化阶段是执行类构造器clinit方法的过程。
1)clinit方法是由编译期自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译期收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在它之前的变量,
定义在它之后的变量,在前面的静态语句块中能赋值,但不能访问。
2)clinit方法与实例构造器init不同,它不需要显式的调用父类构造器,虚拟机会保证在子类的clinit执行之前,父类的clinit已经执行完毕。因此在虚拟机中第一个被执行的clinit方法的类肯定是Object
3)由于父类的clinit方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作
4)clinit方法对于类或者接口来说并不是必须的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不生成clinit方法
5)接口中不能使用静态语句块,但仍然有变量赋值操作,因此接口与类一样都会生成clinit方法。但是接口与类不一样,执行接口的clinit不需要先执行父接口的clinit。只有父接口的变量被使用时父接口才被初始化。
另外,接口的实现类在初始化的时候也一样不会执行接口的clinit
6)虚拟机会保证一个类的clinit在多线程环境中被正确的加锁和同步(执行一次)
分享到:
相关推荐
#### 一、Java与JVM中的Class文件加载机制概述 Java作为一种动态性极强的解释型编程语言,在程序运行时,Java虚拟机(JVM)负责将编译生成的`.class`文件加载到内存中进行执行。在Java环境中,每个类(Class)以及...
"class文件热加载,上传class文件实现热加载"这个主题主要涉及到Java应用的运行时动态更新机制。下面将详细介绍这个过程及其相关知识点。 1. **Java类加载器**: - Java虚拟机(JVM)通过类加载器来加载类。默认有...
2. **字节码操作**:在热加载过程中,可能需要修改已加载的类的字节码。ASM、Javassist等库可以帮助我们动态地生成和修改字节码。 3. **代理模式**:使用Java的代理机制,可以在运行时创建新类的代理,从而实现热...
在Java编程语言中,`class`文件是程序的二...综上所述,`class文件编译器.zip`可能包含了与Java编译相关的工具、教程或示例,涵盖了从源代码到可执行字节码的整个过程,对于学习和理解Java编译机制是非常有价值的资源。
当我们运行一个JAR文件时,Java虚拟机(JVM)会加载其中的class文件并执行其中的代码。 修改JAR文件中的class文件涉及以下几个步骤: 1. **解压JAR文件**:使用解压缩工具(如WinRAR、7-Zip或命令行的jar命令)将...
当源代码发生变化时,JRebel会自动将新版本的Class文件加载到运行中的Java虚拟机(JVM)中,从而使得开发者可以立即看到更改的结果。JRebel支持多种应用服务器和框架,如Tomcat、Jetty、Spring等。 2. **DCEVM...
JVM加载class文件的原理机制 JVM加载class文件的原理机制是Java中的核心机制之一,由于Java中的所有类必须被装载到JVM中才能运行,这个装载工作是由JVM中的类装载器完成的。类装载器所做的工作实质是把类文件从硬盘...
"解决 Java 编译成功后运行 Class 文件出现“找不到或无法加载主类”的问题" 在 Java 开发中,经常会遇到一个问题,即 javac 编译成功后,用 java 运行 Class 文件却出现“找不到或无法加载主类”的错误信息。这种...
“class文件反编译工具”是一种实用的开发辅助工具,它允许开发者查看和理解已编译的Java程序内部结构,这对于学习开源库、调试、逆向工程或者分析恶意软件的行为非常有帮助。其中,`jd-gui`是一个常见的开源Java反...
下面将详细介绍.class 文件反编译到.java 文件的过程,包括反编译工具的使用和反编译后的修改、再编译等步骤。 一、反编译工具的选择 在反编译.class 文件时,需要使用专门的反编译工具。目前有多种反编译工具可供...
3. **加载class文件**:在jd-gui的窗口中,可以通过菜单栏的"File" -> "Open File"选项,选择你要反编译的.class文件。也可以直接拖拽.class文件到jd-gui的主窗口。 4. **查看反编译结果**:一旦加载成功,jd-gui会...
本文将围绕".class文件反编译工具"这一主题,详细讲解如何将`.class`文件转换为可读的`.java`源文件,并重点介绍解压后的jd-gui-windows-1.4.0工具的使用方法。 首先,了解`.class`文件结构至关重要。每个`.class`...
本文将深入探讨“class文件”的解析过程,这是理解Java虚拟机工作原理的关键一步。通过分析《深入理解Java虚拟机》一书中的案例,我们可以更深入地了解这个过程。 首先,class文件是Java编译器将源代码编译后的二...
3. 加载Class文件:通过菜单或直接拖放Class文件到jd-gui窗口中,程序会自动反编译并显示源代码。 4. 浏览代码:在界面上,你可以逐行浏览反编译后的源码,点击类名或方法名可以跳转到相应的定义。 虽然jd-gui在...
在Java开发过程中,有时我们需要查看已编译的`.class`文件来理解代码的执行逻辑,尤其是在遇到异常或问题时,查看字节码有助于排查问题。Eclipse作为一个强大的Java集成开发环境,本身并不直接支持查看`.class`文件...
JVM加载class文件的原理机制是Java虚拟机中一个非常重要的组件,负责将class文件加载到内存中,以便Java程序的执行。下面是JVM加载class文件的原理机制的详细介绍: 类加载的原理 在Java中,所有的类都必须被加载...
然后,可以通过点击“Open File”或直接拖放.class文件到界面中来加载文件。JD-GUI会即时显示反编译后的源代码。配置文件jd-gui.cfg则用于设置JD-GUI的特定选项,如字体、颜色主题等,以适应个人偏好。 此外,JD-...
总结起来,Android 动态加载Class涉及到的关键技术包括:自定义ClassLoader、Dex文件操作、反射、权限管理以及Android的类加载机制。这一技术在提高程序灵活性和维护性的同时,也需要开发者对Android系统的深入了解...
Java虚拟机(JVM)的Class文件原理是Java程序运行的...总的来说,JVM的Class文件原理涉及到类加载器的层次结构、类的加载方式以及类加载的动态机制。理解这些原理对于优化Java程序性能和解决类加载相关问题至关重要。