延吉网站建设depawo,私人订制网站推荐,建设银行官方网站认证,品牌做网站公司文章目录 探索JVM类加载机制1.加载2.连接2.1.验证2.2.准备2.3.解析2.3.1.类和接口的解析2.3.2.字段解析2.3.3.方法解析2.3.4.接口方法解析 3.初始化 Why#xff1f;#xff1a; 其实#xff0c;网络上已经有许多有关JVM的干货分享#xff0c;而且也有许多优质的书籍#x… 文章目录 探索JVM类加载机制1.加载2.连接2.1.验证2.2.准备2.3.解析2.3.1.类和接口的解析2.3.2.字段解析2.3.3.方法解析2.3.4.接口方法解析 3.初始化 Why 其实网络上已经有许多有关JVM的干货分享而且也有许多优质的书籍例如周志明大佬的《深入理解Java虚拟机》还有官方发版的《JAVA虚拟机规范》等。但是鄙人每次温故或者解惑的时候也不会随时随地都有书籍在旁经常上网查询而且内容良莠不齐定位到满足需求文章也浪费时间。所以鄙人就向对技术知识进行具有个人需求特色的归档同时也温习一下阅读过的优质干货和书籍。 以下内容都是基于《深入理解Java虚拟机》和 Oracle的官方文档JAVA SE 8 虚拟机规范 所总结
探索JVM类加载机制
JVM 将类的字节码数据加载到内存并对数据进行校验解析和初始化最终形成可以被运行状态的 JVM 执行的 Class 这一过程称之为类加载机制。注意这一过程都是在程序运行期间完成的详见 Loading, Linking, and Initializing。
在JVM规范中类加载的过程包含加载 Loading- 连接Linking - 初始化Initialization。而连接又分为验证准备和解析。
1.加载 Java虚拟机规范中其实该过程为创建与加载创建主要指类加载器如何委托或定义需加载的类。 加载查找具有特定名称的类或接口的二进制字节流并从将该二进制数据加载至内存并生成一个对应的类或接口。
1通过类的全限定名来获取定义该类的二进制字节流一般指字节码文件。
2将这个字节流锁表示的静态存储结构转化为方法区的运行时数据结构。
3在内存中生存一个表示该类的 java.lang.Class 对象作为方法区这个类的访问入口。
类加载器并不需要等待某个类被首次主动使用时再去加载这个类。JVM虚拟机规范中允许类加载器预料某个类将会被使用就预先加载它预加载如果在预先加载的过程中遇到了字节码文件缺失或者存在错误类加载器必须在程序首次主动使用该类时才报告错误LinkageError 错误。如果这个类一直没有被程序主动使用那么类加载器就不会报错。
在虚拟机启动时会进行预加载加载JAVA_HOME/lib/下的rt.jar下的字节码这个jar包里面的内容是java程序运行时必定会用到的像java.lang.*、java.util.*、java.io.*等等。
2.连接
连接将已经加入到内存的类的二进制字节码数据合并到JVM的运行时环境中并在此期间进行一系列的验证解析。
2.1.验证
字节码文件的详细验证内容可见 Verification of class Files。主要验证内容大致如下
1文件格式验证魔数、版本号、编码等。
2元数据验证语义校验不存在与Java语言规范中为定义的元数据信息。
3字节码验证数据流分析和控制流分析确保代码符合逻辑并不存在不合法的字节码指令。
4符号引用验证将符号引用解析为直接引用时只会能找到对应的资源如类、方法、字段等。
2.2.准备
为类或接口创建静态字段并将这些字段初始化为默认值。在此期间不需要执行任何Java虚拟机代码静态字段的显式初始化是在初始化阶段中类构造器方法clinit()中执行而不是准备阶段。进行内存分配并进行初始化默认值的仅包括静态字段不包括实例字段。在特殊情况下也就是如果字段属性为ConstantValue属性也就是常量字段就会在准备阶段被初始化为常量值即ConstantValue所指定的值。
// 静态字段
// 准备阶段初始化为 0
public static int i 1;// 常量字段
// 准备阶段初始化为 1
public static int i 1;2.3.解析
将运行时常量池中的符号引用动态替换为直接引用具体值的过程。
在Java 虚拟机规范中5.4.3. Resolution 指出以下字节码指令 anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield, putstatic 在执行之前必须确保符号引用已解析。
2.3.1.类和接口的解析
如果当前类为D存在一个还未被解析的符号引用N 指向类或接口C 需要去解析。解析过程如下
1如果当前类C 不是数组类型那么就会由D的定义类加载器The defining class loader 通常意义上代指D的类加载器根据符号引用N 所表示的全限定名去加载C。在加载过程中仍会按照类加载机制从头到尾走一遍所以可能会在验证阶段或者其他阶段出现异常而导致抛出异常。
2如果当前类C 是一个数组类型并且数组元素类型为引用类型即对象那么就会按照上一步根据D 的定义类加载器去加载C中的元素类型然后再由JVM 去生成一个代表该数组维度和元素的数组对象。
3以上步骤执行成功那么表示C 以及完成了类加载过程并且C 是有效并可用的。但是还会效验C 的访问权限如果D 不具备对C 的访问权限则会 throws an IllegalAccessError。
2.3.2.字段解析
如果当前类为D要解析一个未被解析过的字段引用首先解析该字段所属的类或接口C 的符号引用#2.3.1。因此任何在类和接口解析过程中失败出现的异常都可能会导致字段解析失败并抛出相应异常。如果解析成功就对C 和其父类进行字段搜索
1如果C 中声明了一个名称和描述符与字段引用匹配的字段则该声明字段为此次字段搜索的结果。
2否则将会根据C 的继承关系从下到上递归从实现的接口列表及其父接口中搜索字段。
3否则将会根据C 的继承关系从下到上递归从父类中搜索字段。
4否则字段搜索失败throws a NoSuchFieldError。
如果字段搜索成功但是D 不具备该字段的访问权限则 throws an IllegalAccessError。
2.3.3.方法解析
此处与上一小节类似会先解析方法所属类C 的符号引用如果解析失败抛出相应异常。如果类C 解析成功则进行方法搜索
1由于在Class文件格式中类的方法和接口方法的符号引用的常量类型定义是分开的所以如果类C 是一个接口则会throws an IncompatibleClassChangeError。
2在类C 中搜索是否存在简单名称和描述符都与目标匹配的方法如果有则返回目标方法的直接引用。
3在类C 的父类中递归查找如果有则返回目标方法的直接引用。
4在类C 的实现的接口列表及其父类接口中递归查找如果有则返回目标方法的直接引用。 此处特别说明一下在《深入理解Java虚拟机》中有关这一步的说明任务类C是一个抽象类并抛出异常。但是在JavaSE 8的虚拟机规范中5.4.3.3. Method Resolution 有关这一步的说明如下 Otherwise, method resolution attempts to locate the referenced method in the superinterfaces of the specified class C: If the maximally-specific superinterface methods of C for the name and descriptor specified by the method reference include exactly one method that does not have its ACC_ABSTRACT flag set, then this method is chosen and method lookup succeeds.Otherwise, if any superinterface of C declares a method with the name and descriptor specified by the method reference that has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set, one of these is arbitrarily chosen and method lookup succeeds.Otherwise, method lookup fails. 如果在C中存在最具体的父接口方法与方法引用所指定的名称和描述符都匹配的方法并且没有设置ACC_ABSTRACT标记那么方法查找成功。否则如果C中的任意父接口中声明了一个与方法引用所指定的名称和描述符都匹配的方法并且既没有设置ACC_PRIVATE标记也没有设置ACC_STATIC标记那么将会主观选择一个方法查找成功。否则查找失败。 A maximally-specific superinterface method of a class or interface C for a particular method name and descriptor is any method for which all of the following are true: The method is declared in a superinterface (direct or indirect) of C.The method is declared with the specified name and descriptor.The method has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set.Where the method is declared in interface I, there exists no other maximally-specific superinterface method of C with the specified name and descriptor that is declared in a subinterface of I. 在C中最具体的父接口方法需满足特定的方法名称和描述符并且满足以下所有条件 该方法声明在C的父接口直接或间接中该方法能匹配上指定的方法名称和描述符该方法未设置ACC_PRIVATE或者ACC_STATIC标记如果该方法被声明在接口 I 中那么在 I 的子接口中将不会存在任何其他匹配指定方法名称和描述符的在C中最具体父接口方法。 5否则方法解析失败 throws a NoSuchMethodError。
如果方法查找成功但是由于D 不具备该方法的访问权限则 throws an IllegalAccessError。
2.3.4.接口方法解析
先解析方法所属接口C 的符号引用如果解析失败抛出相应异常。如果接口C 解析成功则进行方法搜索
1如果C不是接口则会throws an IncompatibleClassChangeError。
2在接口C 中搜索是否存在简单名称和描述符都与目标匹配的方法如果有则返回目标方法的直接引用。
3否则沿着父接口递归查找直到 java.lang.Object 类接口查找范围也包括了Object类中的方法因为本质上接口就是类并且目标方法设置了ACC_PUBLIC标记不包含ACC_STATIC标记。
4否则方法解析失败 throws a NoSuchMethodError。
3.初始化
类或接口的初始化包括执行其类或接口初始化方法方法。
类或接口最多有一个类或接口初始化方法并通过调用该方法进行初始化。类或接口的初始化方法具有特殊名称clinit()不接受任何参数并且返回类型为void。
clinit()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的编译器收集的顺序是由语句出现在源文件中出现的顺序决定的静态代码块中只能访问到定义在静态代码块之前的变量定义在它之后的变量在前面的静态代码块可以赋值但是不能访问。
public class Test {static {i 10;System.out.println(i); // Illegal forward reference}static int i 0;
}在Java虚拟机规范中规定只能有且只有以下六种情况会对类进行初始化
当遇到 new, getstatic, putstatic, or invokestatic 这四条字节码指令时如果指令中引用的类型还未初始化会触发该类型初始化。 new实例化对象getstatic, putstatic获取或者设置类变量invokestatic调用静态方法 使用 java.lang.reflect 包中的方法对类型进行反射调用如果类型还未初始化会触发其初始化。如果 java.lang.invoke.MethodHandle 实例的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial这四种类型中的一种如果类型还未初始化会触发其初始化。子类进行初始化但是其父类还未初始化会先初始化父类。如果类型继承了一个声明了non-abstract, non-static 方法的接口并且调用了该方法jdk8中接口新增了default方法会导致此接口初始化。当虚拟机启动时指定执行的主类也就是main()所在的类。