标签:必须 bst sse mat 线程安全 match number 实例 间接引用
a类加载必须按加载、验证、准备、初始化、卸载顺序按部就班的开始,但有可能会在一个阶段执行的过程中调用、激活另一个阶段
b解析在一些情况下可以在 初始化 阶段以后开始
c加载阶段和连接阶段部分工作交叉进行
d创建好类以后,随时可以进入准备阶段,但必须在初始化阶段开始之前完成(Preparation may occur at any time following creation but must be completed prior to initialization.)
?
?
a什么时候开始加载,JVM规范没有强制约束????
b接口或类C的创建时由另外个类或接口D触发或D调用JSE类库中的某些方法触发,比如反射等
c如果C是数组,则因数组类没有外部的二进制表示,所以都是JVM创建,不通过类加载器加载(created by the Java Virtual Machine rather than by a class loader)
d无论加载器L直接创建C或授权其他加载器创建C,都称L是C的初始加载器
e JVM支持两种加载器:JVM提供的引导类加载器、用户自定义的类加载器
f在C(或它的父类)的初始加载器抛出ClassNotFoundException错误时,JVM试图验证或解析C,必须抛出NoClassDefFoundError(If the Java Virtual Machine ever attempts to load a class C during verification or resolution (but not initialization), and the class loader that is used to initiate loading of C throws an instance of ClassNotFoundException, then the Java Virtual Machine must throw an instance of NoClassDefFoundError whose cause is the instance of ClassNotFoundException)
g一个功能良好的加载器应当保证三个行为:
g.1如果名称相同,返回的class对象必定相同
g.2无论是L1加载,还是L1委托给L2加载,都必须返回相同Class对象
g.3用户自定义加载器在加载出错时,必须反应加载出错点
h加载数组类A创建过程遵循以下规则
i加载阶段结束后,class文件就会被以指定格式存储在方法区,并在类型数据存放好后,会在堆中生成对应的Class类对象,作为访问方法区中类型数据的外部接口
?
?
a、类加载器(Class Loader): 实现类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作的代码
b、一个JVM可以有多个类加载器,每个类加载器都有一个独立的类名称空间,仅当两个类被同一类加载器加载,才有可能相同,否则必定不同
c、站在JVM角度,加载器有两种:
c1、启动类加载器(Bootstrap ClassLoader),是JVM自身的一部分,具体实现语言根据JVM自身实现决定,可能是JAVA、C、C++等
c2、非启动类加载器,都有JAVA语言实现,独立于JVM,并全部继承java.lang.ClassLoader
d、站在JAVA开发人员的角度,有三层类加载器:
d.1、启动类加载器(Bootstrap ClassLoader):存放在${JAVAHOME}/lib目录或者通过-XBootclasspath参数指定路径,且是JVM指定的文件名(如rt.jar、tool.jar);无法被JAVA直接引用,但可以通过在自定义类加载器中返回null,把加载请求委派给引导类加载器去处理
d.2、扩展类加载器(Extension Class Loader):在sun.misc.Launcher$ExtClassLoader中以JVM代码实现;负责加载${JAVAHOME}/lib/ext目录或java.ext.dirs系统变量指定路径中所有类库,用户可以将通用的类库放置此处来扩展JAVA。可以在程序中直接使用此加载器加载类。JDK9之后被平台类加载器(Platform Class Loader)代替
d.3、平台类加载器(Platform Class Loader)
d.4、应用程序类加载器(Application Class Loader):由sun.misc.Launcher$AppClassLoader实现。负责加载用户类路径(classPath)上所有的类库,如果没有自定义类加载器,则默认为此。
?
a、工作过程:收到一个类加载请求,先将其委派给父类加载器加载,直到最顶层的类启动加载器中,当父类加载器无法完成加载(找不到类),子类加载器才会尝试加载类
b、并不是一个有强制性约束的模型,而是一种类加载器实现的推荐实现
c、能保证类在各个环境中保持唯一
d、自定义类加载器实现代码实例
e、JDK 9模块化后,类加载器的双亲委派机制被第四次破坏
?
a因为JVM虚拟机规范和《深入理解JAVA虚拟机规范(第三版)》对这块介绍差异较大,这里以JVM规范为主要介绍内容, 《深入理解JAVA虚拟机规范(第三版)》内容作为补充
b如果类或接口不满足静态或结构的约束,必须在失败处抛出VerifyError异常
c理解这块,最好先对JVM指令集有初步了解。
?
a检查项包括但不限于:
a.1是否魔数开头
a.2主、次版本是否符合当前JVM范围
a.3常量池中是否存在 不存在或类型错误的常量
a.4常量类型的数据结构是否正确
a.5所有属性的数据结构是否正确
a.6不能缺失内容,尾部不能有多余数据
a.7所有字段/方法引用,必须有有效的名称、类、描述符
b 格式检查不保证某个字段/方法/描述符是否真实存在,只保证格式正确
?
?
指令iload_<n>, fload_<n>, aload_<n>, istore_<n>, fstore_<n>, and astore_<n>默认包含的索引必须小于等于max_locals – 1
指令lload_<n>, dload_<n>, lstore_<n>, and dstore_<n>默认包含的索引必须小于等于max_locals – 2
修饰指令lload, dload, lstore, dstore的wide指令的indexbyte操作数值必须是非负且不大于max_locals – 2;
?
?
这块我看着有点晦涩,后续再搞
?
a、每个类/接口的常量池最多65535个,因为constant_pool_count是16位
b、类/接口可以声明的字段数最多65535,因为fields_count是16位,但fields_count不包含父类/接口继承下来的字段
c、类/接口可以声明的方法数最多65535,methods_count限制,不包括父类/接口继承下来的方法
d、类/接口可以声明的直接父接口最多65535,interfaces_count限制
e、方法调用时创建的栈帧里局部变量表中最大局部变量数位65535个,max_locals和JVM指令集的16位局部变量索引限制。注意long和double会占用两个槽,会减少局部最大数量值
f、方法帧中的操作数栈的最大深度为65535,max_stack限制,同样long和double会占用两个槽,减少最大值
g、方法参数最多255个,由方法描述符的定义限制,如果调用实例/接口方法,这个限制包含this,同样注意long和double
h、数组维度最大255
i、字段/方法名、字段/方法描述符以及其他常量字符串最大长度65535个字符,因为CONSTANT_Utf8_info的16为无符号length限制。注意Utf-8一般用两个或三个字节来编码字符,所以实际最大长度很可能少于65535
?
a此阶段时正式为类中定义的静态变量在方法区中分配内存并设置变量初始值的阶段
b此阶段仅处理类变量,不包括实例变量
c当用final static 声明常量,则会在此此阶段赋具体值
d JVM在此阶段也有些强制约束,这块有点晦涩,,建议看JVM规范英文原文:
d.1假定L1是C的加载器,对于每个在C中的方法m,m覆盖了C的父类或父接口<D,L2>中某个方法,m返回值是Tr,形参从个Tf1....Tfn。
d.1.1如果Tr不是数组,则用T0代替Tr;否则,T0就表示Tr的元素类型
d.1.2从1到n的每个i来说,如果Tfi不是数组类型,Ti表示Tfi,否则Ti就是Tfi的元素类型
d.1.3对于0到n的每个i来说,Ti(L1)=Ti(L2)都该成立
d.2假定C实现了父接口<I,L3>中的方法m,但c自己却没有声明这个方法m,那么就把声明了由C所继承的方法m实现的那个超类标记为<D,L2>( For each instance method m declared in a superinterface <I, L3> of C, if C does not itself declare an instance method that can override m, then a method is selected with respect to C and the method m in <I, L3>. Let <D, L2> be the class or interface that declares the selected method)。JVM也加了同d.1相同的约束。
?
a解析阶段是JVM将常量池内的符号引用替换为直接引用的过程(Resolution is the process of dynamically determining one or more concrete values from a symbolic reference in the run-time constant pool)
b符号引用(symbolic reference):
b.1以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标就行
b.2与JVM实现的内存布局无关,引用的目标不一定是已经加载到JVM内存当中的内容
b.3JVM实现的内存布局可以各不相同,但其能接受的符号引用必须是一致的
c直接引用(Direct reference):
c.1是可以直接指向目标的指针、相对偏移量或者是个能间接定位到目标的句柄
c.2和JVM的内存布局直接相关,同个符号在不同JVM上翻译出来的直接引用一般不相同。
c.3如果有了直接引用,那引用的目标一定已存在于JVM的内存中
d JVM规范未规定解析阶段具体发生时间,只要求了在执行anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, ldc2_w, multianewarray, new, putfield, putstatic等指令时,需要先对符号引用进行解析(Execution of any of these instructions requires resolution of the symbolic reference)
e非invokedynamic指令以外的多次解析同一个符号引用,JVM需要保证在同一个实体中,解析结果必须与第一次解析结果相同,即使第一次解析失败,后续解析成功。
f对于invokedynamic指令发生的符号引用解析,其解析结果仅仅限于当前指令,当前符号引用对于其他指令有效,均为未解析状态(The symbolic reference is still unresolved for all other instructions in the class file, of any opcode, which indicate the same entry in the run-time constant pool as the invokedynamic instruction above)
g符号引用解析过程发生错误,抛出IncompatibleClassChangeError或它子类错误
?
a假设当前代码所处的类为D,需要将未解析的符号引用N解析为接口或类C的引用,解析过程
a.1如果C不是数组类型,JVM会把N的全限定名传给D的类加载器去加载C。在加载C过程中,出现任何失败,均认为当前解析过程失败
a.2如果C是一个数组类型,并且数组的元素类型为对象,JVM将a.1形式加载数组的元素类型,接着JVM生成一个代表数组维度和元素的对象
a.3当执行完前两点,并且无异常,那么C在JVM中已经是个有效的类或接口,但还需要进行访问权限验证。如果访问权限验证失败,则这将抛出IllegalAccessError错误
b D可访问类或接口C的权限条件,需满足以下之一:
b.1 C是Public,且与D处于同一个模块
b.2 C是Public,与D不处于同一个模块,但C所处模块允许被D所处的模块访问
b.3 C不是public,但C和D处于同一个包
?
a要解析一个未被解析过的字段符号引用
a.1首先将会对字段表中class_index项中索引的CONSTANT_Class_info符号(字段所属的类或接口C)引用进行解析,如果解析C失败,则抛出NoSuchFieldError错误
a.2如果C解析成功,且C本身包含了匹配的字段,则返回字段的直接引用。
a.3如果C本身未包含匹配的字段,会按照继承关系,从下往上递归搜索父接口或父类,查找匹配的字段。如果找到了匹配字段,则返回字段的直接引用。否则抛出NoSuchFieldError错误。
a.4如果字段查找成功,则判断字段访问权限,如果不具备访问权限,则抛出IllegalAccessError异常
b字段或方法R可访问权限,需要满足以下条件之一
b.1 R是public
b.2 R是protected,定义在类C中,且D是C的子类或本身
b.3 R是protected或者默认权限,所在类C和类D在同一个包中
b.4 R是private,被声明在D类中
?
a解析方法开始的前提是,所在类C能被正常解析
b如果在类的方法表中发现class_index中索引的C是接口的话,直接抛出IncompatibleClassChangeError错误
c解析过程:
c.1如果C中有同名的方法引用且是签名多态方法(signature polymorphic method declaration),则查找成功。方法描述符中所提到的全部类名也被解析
c.2如果C中方法引用存在相同的名称和描述符,那么查找也成功
c.3如果C有父类,递归重复c.1-c.3步骤,在父类中查找
c.4否则C尝试从父接口去定位方法
c.5如果C中某些最具体的父接口方法(maximally-specific superinterface methods)与方法引用所指定的描述符和名称相符,且存在有且只有一个方法未设置ACC_ABSTRACT标志,那么返回次方法,查找结束
c.6如果C的任意父接口中存在一个与方法引用相同的描述符以及名称的方法,该方法即无ACC_STATIC标志,有没ACC_PRIVATE标志,那么返回此方法,并成功结束
c.7上诉步骤仍找不到,宣告查找失败
d如果方法查找失败,抛出NoSuchMethodError
e如果方法查找成功,但无访问权限,则抛出IllegalAccessError
f类或接口C的最具体的父接口方法,是指满足以下所有条件的任意方法:
f.1方法是C的直接或间接父接口
f.2此方法是根据指定的名称和描述符来声明的
f.3此方法即无ACC_STATIC标志,也没ACC_PRIVATE标志
f.4此方法是唯一存在的
?
a.解析接口方法开始的前提是,所在接口I能被正常解析
b如果在接口的方法表中发现I是类的话,直接抛出IncompatibleClassChangeError错误
c否则,在I的父接口中递归查找,直到Object类,如果存在相同方法,这直接返回
?
?
?
a、初始化阶段,就是执行构造器<clinit>()方法的过程
b、只有发生以下行为时,类或接口才会初始化(A class or interface C may be initialized only as a result of:)
b.1执行指定指令:new、getstatic,putstatic、invokestatic
b.2在初次调用java.lang.invoke.MethodHandle实例时,方法句柄是2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial)
b.3调用反射方法
b.4对某个类的子类进行初始化
b.5如果接口c声明了一个非抽象、非静态的方法,有类直接或间接引用此接口的类初始化(If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.)
b.6被指定为jvm启动时初始类
c在类或接口初始化之前,当前类或接口必须已经通过验证、准备阶段。
d直到初始化阶段,JVM才真正开始执行类中编写的Java代码,将主导权交给app
e每个类或接口都有个唯一的初始化锁,锁的实现有JVM实现自定义
f JVM必须保证一个类的初始化线程安全。
?
注:
1:执行初始化过程,线程的中断状态不受影响
2:抛出的异常E,不是Error或其子类,那就为E创建个ExceptionInInitializerError实例代替它,如果OOM问题,则用OOM错误代替E
3:利用ConstantValue属性初始化类中每个final static字段(Then, initialize each final static field of C with the constant value in its ConstantValue attribute, in the order the fields appear in the ClassFile structure.)
4:初始化方法<clinit>
标签:必须 bst sse mat 线程安全 match number 实例 间接引用
原文地址:https://www.cnblogs.com/adeveloper/p/13020683.html