实战Java虚拟机笔记 9-10

class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对class文件结构做出一些调整,但是其基本的结构和框架是非常稳定的。

class文件的结构:
魔数、小版本号、大版本号、常量池、访问标记、
当前类、
父类、实现的接口、类的字段、类的方法、类的属性。

ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count - 1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interface_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_info];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

魔数:0xCAFEBABE
在常量池数量之后,就是常量池的实际内容,每一项以类型、长度、内容或者类型、内容的格式依次存放。
Utf8    1
Integer 3
Float    4
Long    5
Double    6
Class    7
String    8
Fieldref 9
Methodref 10
NameAndType 12
作为常量池底层的数据类型CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, CONSTANT_Double分别表示UTF8字符串、整数、浮点数、长整数和双精度浮点数。
UTF8的常量经常被其他类型的常量引用。
当使用final定义一个数字常量时,class文件中就会生成一个数字的常量。


CONSTANT_NameAndType的descriptor_index指向的字符串使用了一组特定的字符串来表示类型:
B(byte) S(short) I(int) J(long) F(float) D(double) C(char) Z(boolean) V(void) [(数组) L;(对象)
如:"Ljava/lang/Object;"表示类java.lang.Object。
"[[Ljava/lang/String;"表示String二维数组。

对于类的方法和字段,分别使用CONSTANT_Methodref和CONSTANT_Fieldref表示。两者结构一样,tag的值不同。
CONSTANT_Methodref_info {
  u1 tag;
  u2 class_index;
  u2 name_and_type_index;
}

在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于表明该类的访问信息,如public、final、abstract等。

当前类、父类和接口
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
其中,this_class、super_class指向常量池中一个CONSTANT_Class,interfaces中的每一项也指向常量池中的CONSTANT_Class(这里就必须是接口,而不是类)。

Class文件的字段:
u2 fields_count;
field_info fields[fields_count];
其中,
field_info {
  u2 access_flags;
  u2 name_index;
  u2 descriptor_index;
  u2 attributes_count;
  attribute_info attributes[attributes_count];
}
一个字段可能拥有一些属性,如初始值。这些信息存储在attributes数组中。如:
ConstantValue_attribute {
  u2 attribute_name_index;
  u4 attribute_length;
  u2 constantvalue_index;
}

Class文件的方法:
方法信息和字段信息类似
u2 methods_count;
method_info methods[methods_count];
其中,
method_info {
  u2 access_flags;
  u2 name_index;
  u2 descriptor_index;
  u2 attributes_count;
  attribute_info attributes[attributes_count];
}
descriptor_index指向的字符串有特定格式。如果方法为: Object m(int i, double d, Thread t){...},则方法描述为: (IDLjava/lang/Thread;)Ljava/lang/Object;
和字段类似,方法也可以附带若干个属性,用于描述一些额外信息,比如方法字节码等。
attribute_info {
  u2 attribute_name_index;
  u4 attribute_length;
  u1 info[attribute_length];
}


系统装载Class类型可以分为加载、连接和初始化3个步骤。
Class只有在必须要使用的时候才会被加载。一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用,主动使用只有下列几种情况:
1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
3. 当使用类或接口的静态字段时(final常量除外),比如,使用getstatic或者putstatic指令。
4. 当使用java.lang.reflect包中的方法反射类的方法时。
5. 当初始化子类时,要求先初始化父类。
6. 作为启动虚拟机含有main方法的那个类。

类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行Java字节码。初始化阶段的重要工作是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的,它是由类静态成员的赋值语句以及static语句块合并产生的。
Java编译器并不会为所有的类都产生<clinit>初始化函数。如果一个类既没有赋值语句,也没有static语句块,那么生成的<clinit>函数就应该为空,因此,编译器就不会为该类插入<clinit>函数。

ClassLoader主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。因此,ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的连接和初始化行为。

在标准的Java程序中,Java虚拟机会创建3类ClassLoader为整个应用程序服务。它们分别是BootStrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)和App ClassLoader(应用类加载器,也称为系统类加载器)。
在这些ClassLoader中,启动类加载器最为特别,它是完全由C代码实现的,并且在Java中没有对象与之对应。系统的核心类就是由启动类加载器进行加载的,它也是虚拟机的核心组件。扩展类加载器和应用类加载器都有对应的Java对象可供使用。

无法在Java代码中访问启动类加载器,当试图获得一个类的ClassLoader时,如果得到的是null,这并不意味着没有加载器为它服务,而是指加载那个类的为启动类加载器。
启动类加载器负责加载系统的核心类,比如rt.jar中的Java类。
扩展类加载器负责加载%JAVA_HOME%/lib/ext/*.jar中的Java类。
应用类加载器负责加载用户类,也就是用户程序的类。

系统中的ClassLoader在协同工作时,默认会使用双亲委托模式。即在类加载的时候,系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。
判断类是否加载时,应用类加载器会顺着双亲路径往上判断,直到启动类加载器。但是类加载器不会往下询问,这个委托路线是单向的。

通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如,在系统类中,提供了一个接口,该接口需要在应用中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。拥有这种问题的组件有很多,比如JDBC、Xml Parse等。

在Java平台中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,通常可以称为Service Provider Interface,即SPI。
在启动类加载器加载的类中,可以通过Thread.currentThread().getContextClassLoader()获取应用类加载器。因为在默认情况下,上下文加载器就是应用类加载器。

猜你喜欢

转载自blog.csdn.net/sumaoqing123/article/details/83479443