类的加载与初始化

类的加载

 

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的

类的初始化

什么情况下需要开始类加载过程的第一个阶段:"加载"。虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。

  1. 创建类的实例
  2. 访问类的静态变量(除常量【被final修辞的静态变量】原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
  3. 访问类的静态方法
  4. 反射如(Class.forName("my.xyz.Test"))
  5. 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
  6. 虚拟机启动时,定义了main()方法的那个类先初始化

以上情况称为称对一个类进行“主动引用”,除此种情况之外,均不会触发类的初始化,称为“被动引用”

当创建类对象时,先初始化静态变量和静态块,然后是非静态变量和非静态代码块,然后是构造器。由于静态成员只会被初始化一次,所以如果静态成员已经被初始化过,将不会被再次初始化。对于静态成员,不仅是初始化对象时才会初始化,当第一次引用静态变量或者静态函数时都会使静态成员初始化。

接口的加载过程与类的加载过程稍有不同。接口中不能使用static{}块。当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正在使用到父接口时(例如引用接口中定义的常量)才会初始化。

被动引用例子

  1. 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。。对于静态字段,只有直接定义这个字段的类才会被初始化.
  2. 通过数组定义来引用类,不会触发类的初始化
  3. 访问类的常量,不会初始化类
class SuperClass {
	static {
		System.out.println("superclass init");
	}
	public static int value = 123;
}
 
class SubClass extends SuperClass {
	static {
		System.out.println("subclass init");
	}
}
 
public class Test {
	public static void main(String[] args) {
		System.out.println(SubClass.value);// 被动应用1
		SubClass[] sca = new SubClass[10];// 被动引用2
	}
}

程序运行输出    superclass init 

                            123

从上面的输入结果证明了被动引用1与被动引用2

class ConstClass {
	static {
		System.out.println("ConstClass init");
	}
	public static final String HELLOWORLD = "hello world";
}
 
public class Test {
	public static void main(String[] args) {
		System.out.println(ConstClass.HELLOWORLD);// 调用类常量
	}
}

程序输出结果

hello world

从上面的输出结果证明了被动引用3

加载

 “加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事情:

       1、 通过一个类的全限定名来获取定义此类的二进制字节流。

       2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

       3、 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

      加载阶段即可以使用系统提供的类加载器在完成,也可以由用户自定义的类加载器来完成。加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

       Java语言本身是相对安全的语言,使用Java编码是无法做到如访问数组边界以外的数据、将一个对象转型为它并未实现的类型等,如果这样做了,编译器将拒绝编译。但是,Class文件并不一定是由Java源码编译而来,可以使用任何途径,包括用十六进制编辑器(如UltraEdit)直接编写。如果直接编写了有害的“代码”(字节流),而虚拟机在加载该Class时不进行检查的话,就有可能危害到虚拟机或程序的安全。

      不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证:文件格式验证、元数据验证、字节码验证和符号引用验证。

       1、文件格式验证,是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。如验证魔数是否0xCAFEBABE;主、次版本号是否正在当前虚拟机处理范围之内;常量池的常量中是否有不被支持的常量类型……该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区中,经过这个阶段的验证后,字节流才会进入内存的方法区中存储,所以后面的三个验证阶段都是基于方法区的存储结构进行的。

       2、元数据验证,是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法……

       3、字节码验证,主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。

       4、符号引用验证,发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在“解析阶段”中发生。验证符号引用中通过字符串描述的权限定名是否能找到对应的类;在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问

验证阶段对于虚拟机的类加载机制来说,不一定是必要的阶段。如果所运行的全部代码确认是安全的,可以使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。

准备

 准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

        public static int value=123;//在准备阶段value初始值为0 。在初始化阶段才会变为123 。

解析

 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

       符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

       直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。

初始化

 类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

        初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。

题目分析

class SingleTon {
	private static SingleTon singleTon = new SingleTon();
	public static int count1;
	public static int count2 = 0;
 
	private SingleTon() {
		count1++;
		count2++;
	}
 
	public static SingleTon getInstance() {
		return singleTon;
	}
}
 
public class Test {
	public static void main(String[] args) {
		SingleTon singleTon = SingleTon.getInstance();
		System.out.println("count1=" + singleTon.count1);
		System.out.println("count2=" + singleTon.count2);
	}
}

错误答案

count1=1

count2=1

 正确答案

count1=1

count2=0

分析:

1:SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2:类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3:类初始化化,因为是SingleTon.getInstance()这种初始化只会初始化静态变量和执行静态代码块儿和执行getInstance()方法,但这时第一句是private static SingleTon singleTon = new SingleTon(); 这一句给singleton分配了一块儿内存空间并且进行初始化,因为静态变量只能初始化一次并且只要初始化开始那么这次的初始化就占住了位置只能由他来初始化,所以这些静态变量只能由SingleTon.getInstance()这次初始化来完成,new SingleTon()的这次初始化就是初始化非静态成员变量和构造方法 这时conunt1=1,count2=2,然后new的初始化完毕,之后再继续初始化静态变量(给静态变量赋值)count1不变为1;count2=0;再执行SingleTon.getInstance()把值赋给外面的singleTon;

5:继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0

7.Class 文件格式

全限定名称: JVM中的决定路径,包名加类名

非全限定名称: 类名

字段描述符: 表示一个字段的类型

方法描述符: 表示一个方法需要的参数和返回值

所有方法(Method),包括实例初始化方法和类初始化方法(§2.9)在内,都由 method_info

结构所定义。在一个 Class 文件中,不会有两个方法同时具有相同的方法名和描述符,在方法的Attributes属性中有code[]数组存储方法编译过后的字节码

常量池:不仅包括我们定义的常量和字符串,还有一大堆其他的符号,例如字段描述符、方法描述符、类的全限定和非全限定名

属性 Attributes: 

code属性:在java虚拟机规范的 4.7.3属性中有详细介绍这里简单说几个重要

max_stack 给出了当前方法的操作数栈在运行执行的任何时间点的最大深度

max_locals 给出了分配在当前方法引用的局部变量表中的局部变量个数,包括
调用此方法时用于传递参数的局部变量

code[]数组 给出了实现当前方法的 Java 虚拟机字节码。

和一些关于异常的

InnerClasses属性:  如果 Class 中包含某些类或者接口,那么它的常量池(以及对应的 InnerClasses 属性)必须包含这些成员,即使某些类或者接口没有被这个 Class 使用过。这条规则暗示着内部类或内部接口成员在其被定义的外部类(Enclosing Class)中都会包含有它们的 InnerClasses 信息。

Signature属性: 位于 ClassFile(§ 4.1), field_info(§ 4.5)
或 method_info(§ 4.6)结构的属性表中。在 Java 语言中,任何类、 接口、 初始化方法或成
员的泛型签名如果包含了类型变量(Type Variables) 或参数化类型(Parameterized
Types),则 Signature 属性会为它记录泛型签名信息。

LineNumberTable属性:  它被调试器用于确定源文件中行号表示的内容在 Java 虚拟机的 code[]数组中对应的部分


LocalVariableTable属性: 它被调试器用于确定方法在执行过程中局部变量的信息。


LocalVariableTypeTable属性:  它被用于给调试器确定方法在执行中局部变量的信息LocalVariableTypeTable 属性和 LocalVariableTable 属性并不相同,
LocalVariableTypeTable 提供签名信息而不是描述符信息。这仅仅对泛型类型有意义。泛型
类型的属性会同时出现在 LocalVariableTable 属性和 LocalVariableTypeTable 属性中,
其他的属性仅出现在 LocalVariableTable 属性表中。

Java 虚拟机将普通方法、实例初始化方法(§2.9)或类和接口初始化方法(§2.9)的代

码存储在 Class 文件 method_info 结构里的 Code 属性的 code[]数组中。

8.Class文件校验

链接期校验还有助于增强解释器的执行性能,因为解释器在运行期无需再对每个执行指令进行

检查。Java 虚拟机在运行期可以假设所有必要的校验都已经执行过。例如,Java 虚拟机可以确

保以下内容:

 操作数栈不会发生上限或下线溢出。

 所有局部变量的使用和存储都是有效的。

 所有 Java 虚拟机指令都拥有正确的参数类型。

校验器也会对那些不是用 Code 属性(§4.7.3)中 code[]数组的内容进行校验,这些检验

包括如下内容:

 确保 final 类不会有子类,以及 final 方法不会被覆盖(§5.4.5)。

 确保除了 Object 之外的每一个类都有直接父类。

 确保常量池满足文档静态约束:例如常量池中所有 CONSTANT_Class_info 结构所包含

的 name_index 项都是一个指向 CONSTANT_Utf8_info 项的有效常量池索引。

 确保常量池之中所有字段引用和方法引用都有有效的名称、类型和方法描述符。

类的加载:

Java 虚拟机动态地加载、链接与初始化类和接口。加载是根据特定名称查找类或接口类型的

二进制表示(Binary Representation),并由此二进制表示创建类或接口的过程。链接是为

了让类或接口可以被 Java 虚拟机执行,而将类或接口并入虚拟机运行时状态的过程。类或接口的

初始化是指执行类或接口的初始化方法<clinit>(§2.9)。

Java 虚拟机为每个类型都维护一个常量池(§2.5.5)。它是 Java 虚拟机中的运行时数据

结构,像传统编程语言实现中的符号表一样有很多用途。

当类或接口创建时(§5.3),它的二进制表示中的 constant_pool 表(§4.4)被用来构

造运行时常量池。运行时常量池中的所有引用最初都是符号引用。这些符号引用来自于类或接口的

二进制表示的如下结构中:

某个类或接口的符号引用来自于类或接口二进制表示中的 CONSTANT_Class_info 结构

(§4.4)。这种引用提供的类或接口的名称如同 Class.getName()方法返回值的格式一样,

也就是说:

 对于非数组的类或接口,那就是类或接口的二进制名称(§4.2.1)。

 对于一个 M 维的数组类,名称会以 M 个 ASCII 字符"["开头,随后是数组元素类型的

表示:

 如果数组的元素类型是 Java 原始类型之一,它就以相应的字段描述符(§4.3.2)

来表示。

 否则,如果数组元素类型是某种引用类型,它就以 ASCII 字符"L"加上二进制名称,并以 ASCII 字符";"结尾的字符串表示。

 在本章中,无论何时提到类或接口的名称,读者都可以按 Class.getName 方法的返回值的

格式来理解。

 类或接口的某个字段的符号引用来自于类或接口二进制表示中的

CONSTANT_Fieldref_info 结构(§4.4.2)。这种引用包含了字段的名称和描述符,及指

向字段所属类或接口的符号引用。

 类中某个方法的符号引用来自于类或接口二进制表示中的 CONSTANT_Methodref_info 结

构(§4.4.2)。这种引用提供方法的名称和描述符,及指向方法所属类的符号引用。

 接口的某个方法的符号引用来自于类或接口二进制表示中的

CONSTANT_InterfaceMethodref_info 结构(§4.4.2)。这种引用提供接口方法的名称

和描述符,及指向方法所属接口的符号引用。

 方法句柄(Method Handle)的符号引用来自于类或接口二进制表示中的

CONSTANT_MethodHandle_info 结构(§4.4.8)。

 方法类型(Method Type)的符号引用来自于类或接口二进制表示中的

CONSTANT_MethodType_info 结构(§4.4.9)。

 调用点限定符(Call Site Specifier)的符号引用来自于类或接口二进制表示的

CONSTANT_InvokeDynamic_info 结构(§4.4.10)。这种引用包含了:

 方法句柄的符号引用,为 invokedynamic 指令的引导方法(Bootstrap Method)

来提供服务。

 一系列符号引用(到类、方法类型和方法句柄)、字符常量和运行时常量(如 Java 原生

数值类型的值),它们将作为静态参数(Static Arguments)提供给引导方法。

 调用方法的名称与描述符

另外,一些运行时数值它不是由符号引用,而是由 constant_pool 表中某些项的值来决定:

 字符常量表示 String 类实例的一个引用,它来自于类或接口二进制表示的

CONSTANT_String_info 结构(§4.4.3)。CONSTANT_String_info 结构包含了由Unicode 码点(Code points)序列来组成字符常量。 Java 语言需要全局统一的字符常量(这就意味着如果不同字面量(Literal)包含着相 同的码点序列,就必须引用着相同的 String 类的实例(JLS §3.10.5))。此外,在任意 字符串上调用 String.intern 方法,如果那个字符串是字面量的话,方法的结果应当是对 相同字面量的 String 实例的引用。因此, ("a" + "b" + "c").intern() == "abc" 必须返回 true。 为了得到字符常量,Java 虚拟机需要检查 CONSTANT_String_info 结构中的码点序

列。

 如果 String.intern 方法以前曾经被某个与 CONSTANT_String_info 结构中的码

点序列一样的 String 实例调用过,那么此次字符常量获取的结果将是一个指向相同

String 实例的引用。

 否则,一个新的 String 实例会被创建,它会包含着指定 CONSTANT_String_info

结构中 Unicode 码点序列;字符常量获取的结果是指向那个新 String 实例的引用。

最后,新 String 实例的 intern 方法被 Java 虚拟机自动调用。

 其它运行时常量值来自于类或接口二进制表示的 CONSTANT_Interger_info、

CONSTANT_Float_info、CONSTANT_Long_info 或是 CONSTANT_Double_info 结构

(§4.4.4,§4.4.5)。请注意,这里 CONSTANT_Float_info 结构的值以 IEEE 754 单

精度浮点格式表示,CONSTANT_Double_info 结构的值以 IEEE 754 双精度浮点格式表示

(§4.4.4,§4.4.5)。来自这些结构的运行时常量值必须可以用 IEEE 754 单或双精度浮

点格式分别表示。

在类或接口的二进制表示中,constant_pool 表中剩下的结构还有

CONSTANT_NameAndType_info(§4.4.6)和 CONSTANT_Utf8_info(§4.4.7),它们被

间接用以获得对类、接口、方法、字段、方法类型和方法句柄的符号引用,或是在需要得到字符常

量和调用点限定符时被引用。

数组类型没有外部的二进制表示;它们都是由 Java 虚拟机创建,而不是

通过类加载器加载的。

类加载器加载了class文件后会返回一个Class对象

链接:验证、准备、解析

验证: 验证就是Class文件校验,验证(Verification,§4.10)阶段用于确保类或接口的二进制表示结构上是正确的

准备: 准备(Preparation)阶段的任务是为类或接口的静态字段分配空间,并用默认值初始化这

些字段(§2.3,§2.4)。这个阶段不会执行任何的虚拟机字节码指令。在初始化阶段(§5.5)

会有显式的初始化器来初始化这些静态字段,所以准备阶段不做这些事情。

在类的对象创建之后的任何时间,都可以进行准备阶段,但它得确保一定要在初始化阶段开始

前完成。

解析: 解析(Resolution)是根据运行时常量池的符号引用来动态决定具体的值的过程。

我理解的解析:假设A类的常量池中有一个 类B的符号引用,解析就是把这个符号引用换成类B的地址

下面描述对于类或接口 D 的运行时常量池(§5.1)中的符号引用进行解析的过程。

解析细节会因为符号引用类型的不同而有所区别。

(1).类与接口解析

Java 虚拟机为了解析 D 中对标记为 N 的类或接口 C 的未解析符号引用,就会执行下列步骤:

 D 的定义类加载器被用来创建标记为 N 的类或接口。这个类或接口就是 C。在创建类或接

口的过程中,如果有任何异常发生,可以被认为是类和接口解析失败而抛出。过程的细节在 5.3

节进行描述。

 如果 C 是数组类并且它的元素类型是引用类型,那么表示元素类型的类或接口的符号引

用会按照 5.4.3.1 节的算法通过递归调用来解析。

 最后,检查 C 的访问权限:

 如果 C 对 D 是不可见的(§5.4.4),类或接口解析抛出 IllegalAccessError 异常。

第 4 步这种情况有可能发生,譬如,C 是一个原来被声明为 public 的类,但它在 D 编译后被

改成非 public。

如果第 1 步和第 2 步成功但是第 3 步失败,C 仍然是有效、可用的。当然,整个解析过程将

被认为是失败的,并且 C 将会拒绝 D 访问请求。

(2).字段解析

为了解析 D 中一个未被解析的类或接口 C 的字段的符号引用,字段描述符中提供的对 C 的符

号引用应该首先被解析

当解析一个字段引用时,字段解析会在 C 和它的父类中先尝试查找这个字段:

a. 如果 C 中声明的字段与字段引用有相同的名称及描述符,那么此次查找成功。字段查找的

结果是 C 中那个声明的字段。

b. 不然的话,字段查找就会被递归应用到类或接口 C 的直接父接口上。

c. 否则,如果 C 有一个父类 S,字段查找也会递归应用到 S 上。

d. 如果还不行,那么字段查找失败。

(3).方法解析

为了解析 D 中一个对类或接口 C 中某个方法的未解析符号引用,方法引用中包含的对 C 的符

号引用应该首先被解析。

当解析一个方法引用时:

1. 首先检查方法引用中的 C 是否类或接口。

 如果 C 是接口,那么方法引用就会抛出 IncompatibleClassChangeError 异常。

2. 方法引用解析过程会检查 C 和它的父类中是否包含此方法:

 如果 C 中确有一个方法与方法引用的指定名称相同,并且声明是签名多态方法

(Signature Polymorphic Method,§2.9),那么方法的查找过程就被认为是成

功的。所有方法描述符中所提到的类也需要解析(§5.4.3.1)。

这次解析的方法是签名多态方法。对于 C 来说,没有必要使用方法引用指定的描述符来

声明方法。

 否则,如果 C 声明的方法与方法引用拥有同样的名称与描述符,那么方法查找也是成功。

 如果 C 有父类的话,那么如第 2 步所述的查找方式递归查找 C 的直接父类。

3. 另外,方法查找过程也会试图从 C 的父接口中去定位所引用的方法。

 如果 C 的某个父接口中确实声明了与方法引用相同的名称与描述符的方法,那么方法查

找成功。

 否则,方法查找失败。

(4).接口方法解析

为了解析 D 一个对 C 中接口方法的未解析符号引用,接口方法引用中到接口 C 的符号引用应

该最先被解析(§5.4.3.1)。因此,在解析接口引用时出现的任何异常都可以当作接口方法引用

解析的异常而被抛出。如果对接口引用被成功地解析,接口方法引用解析相关的异常就可以被抛出。

当解析接口方法引用时:

 如果 C 不是一个接口,那么接口方法解析就会抛出 IncompatibleClassChangeError

异常。

 否则,如果在 C 与它的父接口(或 Object 类)中也不存在与接口方法描述符相同的方

法的话,接口方法解析会抛出 NoSuchMethodError 异常。

(5).访问控制

一个类或接口 C 对另外一个类或接口 D 是可见的(Accessible),当且仅当下面的条件之一

成立:

C 是 public 的。

 C 和 D 处于同一个运行包(Runtime Package,§5.3)下面。

一个字段或方法 R 对另外一个类或接口 D 可见的,当且仅当下面的条件之一成立:

 R 是 public 的。

 R 在 C 中是 protected,那么 D 要么与 C 是同一个类,要么就是 C 的子类。如果 R 不是 static

的,那么到 R 的符号引用就必须包含一个到 T 类的符号引用,这里的 T 要么与 D 相同,

要么就是 D 的子类或父类。

 R 要么是 protected,要么是默认访问权限(既不是 public,也不是 protected,更

不是 private,并且它所属的那个类应该与 D 处于同一运行包下)。

 R 是 private 的,它声明在 D 类中。

初始化:

初始化(Initialization)对于类或接口来说,就是执行它的初始化方法(§2.9)。在发

生下列行为时,类或接口将会被初始化:

 在执行下列需要引用类或接口的 Java 虚拟机指令时:new,getstatic,putstatic

或 invokestatic。这些指令通过字段或方法引用来直接或间接地引用其它类。执行上

面所述的 new 指令,在类或接口没有被初始化过时就初始化它。执行上面的 getstatic,

putstatic 或 invokestatic 指令时,那些解析好的字段或方法中的类或接口如果还

没有被初始化那就初始化它。

 在初次调用 java.lang.invoke.MethodHandle 实例时,它的执行结果为通过 Java

虚拟机解析出类型是 2(REF_getStatic)、4(REF_putStatic)或者 6

(REF_invokeStatic)的方法句柄(§5.4.3.5)。

 在调用 JDK 核心类库中的反射方法时,例如,Class 类或 java.lang.reflect 包。

 在对于类的某个子类的初始化时。

 在它被选定为 Java 虚拟机启动时的初始类(§5.2)时。

在类或接口被初始化之前,它必须被链接过,也就是经过验证、准备阶段,且有可能已经被解

析完成了。

因为 Java 虚拟机是支持多线程的,所以在初始化类或接口的时候要特别注意线程同步问题,

可能其它一些线程也想要初始化相同名称的类或接口。也有可能在初始化一些类或接口时,初始的

请求被递归要求初始化它自己。Java 虚拟机实现需要负责处理好线程同步和递归初始化,具体可

以使用下面的步骤来处理。这些处理步骤假定 Class 对象已经被验证和准备过,并且处于下面所

述的四种状态之一:

 Class 对象已经被验证和准备过,但还没有被初始化。

 Class 对象正在被其它特定线程初始化。

 Class 对象已经成功被初始化且可以使用。

 Class 对象处于错误的状态,可能因为尝试初始化时失败过

每个类或接口 C,都有一个唯一的初始化锁 LC。如何实现从 C 到 LC 的映射可由 Java 虚拟机

实现自行决定。例如,LC 可以是 C 的 Class 对象,或者是与 Class 对象相关的管程(Monitor)。

初始化 C 的过程如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值