之前的文章介绍了class文件解析常量池的步骤.其属于ClassFileParser::parseClassFile的核心步骤的第三步.本文介绍第4-6步.
首先回顾一下ClassFileParser::parseClassFile的核心步骤:
-
读取魔数并进行校验其是否为0xCAFEBABE
-
读取class文件主,次版本号及验证,检查版本号支持是否支持,若不支持,则抛出java.lang.UnsupportedClassVersionError
-
解析常量池
-
读取访问标识
- 处理JVM_ACC_INTERFACE的后向兼容问题,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
- 验证类的修饰符并设置
-
读取当前类索引,并按索引在常量池项中找到当前类的全限定名
-
读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄
- 如果是为0的话,则检查其类名是否是java.lang.Object
- 检查其对应的类型为klass,检查其类型不是数组,如果是数组,则抛出异常
-
读取接口信息,接口类型包括本地接口和父类传递接口
- 如果接口不存在,则返回一个空数组
- 解析接口
-
解析类变量,并计算出域大小和偏移量(oop-map)信息,并根据域分配策略对字段存储顺序进行分配.这些信息都将在后续步骤填入instanceKlass对象中成为类信息的一部分
-
读取方法信息.根据从class中解析出的method信息创建了methodOop对象
-
解析父类
-
根据已解析的父类,方法,接口等信息得到vtable,itable 大小
-
计算字段的偏移量
-
创建当前类instanceKlass并按照上述步骤已解析好的信息为该对象赋值,在这期间,同时创建了JAVA镜像类(mirror)
-
解析类文件中的属性
-
通知类已加载,跟新perfdata计数器
验证访问标识
这部分的代码如下:
// 4. Access flags 读取访问标识
AccessFlags access_flags;
jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS; // (0x0001 | 0x0010 | 0x0020 | x0200 | 0x0400 | 0x2000 |0x4000 | x1000)
// 4.1 处理JVM_ACC_INTERFACE的后向兼容问题
if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility 如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
flags |= JVM_ACC_ABSTRACT;
}
// 4.2 验证类的修饰符并设置
verify_legal_class_modifiers(flags, CHECK_(nullHandle));
access_flags.set_flags(flags);
AccessFlags 内部持有一个名为_flags的字段,并定义了多个方法来判断是否符合访问标记.
此外,在hotspot/src/share/vm/utilities/accessFlags.hpp 中还定义了一个枚举,定义了各种类型的访问标记.
enum {
// See jvm.h for shared JVM_ACC_XXX access flags
// HotSpot-specific access flags
// flags actually put in .class file
JVM_ACC_WRITTEN_FLAGS = 0x00007FFF,
// methodOop flags
JVM_ACC_MONITOR_MATCH = 0x10000000, // True if we know that monitorenter/monitorexit bytecodes match
JVM_ACC_HAS_MONITOR_BYTECODES = 0x20000000, // Method contains monitorenter/monitorexit bytecodes
JVM_ACC_HAS_LOOPS = 0x40000000, // Method has loops
JVM_ACC_LOOPS_FLAG_INIT = (int)0x80000000,// The loop flag has been initialized
JVM_ACC_QUEUED = 0x01000000, // Queued for compilation
JVM_ACC_NOT_C2_COMPILABLE = 0x02000000,
JVM_ACC_NOT_C1_COMPILABLE = 0x04000000,
JVM_ACC_NOT_OSR_COMPILABLE = 0x08000000,
JVM_ACC_HAS_LINE_NUMBER_TABLE = 0x00100000,
JVM_ACC_HAS_CHECKED_EXCEPTIONS = 0x00400000,
JVM_ACC_HAS_JSRS = 0x00800000,
JVM_ACC_IS_OLD = 0x00010000, // RedefineClasses() has replaced this method
JVM_ACC_IS_OBSOLETE = 0x00020000, // RedefineClasses() has made method obsolete
JVM_ACC_IS_PREFIXED_NATIVE = 0x00040000, // JVMTI has prefixed this native method
JVM_MH_INVOKE_BITS // = 0x10001100 // MethodHandle.invoke quasi-native
= (JVM_ACC_NATIVE | JVM_ACC_SYNTHETIC | JVM_ACC_MONITOR_MATCH),
// klassOop flags
JVM_ACC_HAS_MIRANDA_METHODS = 0x10000000, // True if this class has miranda methods in it's vtable
JVM_ACC_HAS_VANILLA_CONSTRUCTOR = 0x20000000, // True if klass has a vanilla default constructor
JVM_ACC_HAS_FINALIZER = 0x40000000, // True if klass has a non-empty finalize() method
JVM_ACC_IS_CLONEABLE = (int)0x80000000,// True if klass supports the Clonable interface
JVM_ACC_HAS_FINAL_METHOD = 0x01000000, // True if klass has final method
// klassOop and methodOop flags
JVM_ACC_HAS_LOCAL_VARIABLE_TABLE= 0x00200000,
JVM_ACC_PROMOTED_FLAGS = 0x00200000, // flags promoted from methods to the holding klass
// field flags
// Note: these flags must be defined in the low order 16 bits because
// instanceKlass only stores a ushort worth of information from the
// AccessFlags value.
// These bits must not conflict with any other field-related access flags
// (e.g., ACC_ENUM).
// Note that the class-related ACC_ANNOTATION bit conflicts with these flags.
JVM_ACC_FIELD_ACCESS_WATCHED = 0x00002000, // field access is watched by JVMTI
JVM_ACC_FIELD_MODIFICATION_WATCHED = 0x00008000, // field modification is watched by JVMTI
// flags accepted by set_field_flags()
JVM_ACC_FIELD_FLAGS = 0x00008000 | JVM_ACC_WRITTEN_FLAGS
};
此处,首先获得到访问标识后,与JVM_RECOGNIZED_CLASS_MODIFIERS 进行与运算,得到最终的标记. JVM_RECOGNIZED_CLASS_MODIFIERS为宏,定义在hotspot/src/share/vm/prims/jvm.h 中,如下:
#define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC | \
JVM_ACC_FINAL | \
JVM_ACC_SUPER | \
JVM_ACC_INTERFACE | \
JVM_ACC_ABSTRACT | \
JVM_ACC_ANNOTATION | \
JVM_ACC_ENUM | \
JVM_ACC_SYNTHETIC)
宏展开后的结果如下: (0x0001 | 0x0010 | 0x0020 | x0200 | 0x0400 | 0x2000 |0x4000 | x1000)
接下来,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容.
最后,验证类的修饰符是否格式正确,代码如下:
void ClassFileParser::verify_legal_class_modifiers(jint flags, TRAPS) {
if (!_need_verify) { return; }
const bool is_interface = (flags & JVM_ACC_INTERFACE) != 0;
const bool is_abstract = (flags & JVM_ACC_ABSTRACT) != 0;
const bool is_final = (flags & JVM_ACC_FINAL) != 0;
const bool is_super = (flags & JVM_ACC_SUPER) != 0;
const bool is_enum = (flags & JVM_ACC_ENUM) != 0;
const bool is_annotation = (flags & JVM_ACC_ANNOTATION) != 0;
const bool major_gte_15 = _major_version >= JAVA_1_5_VERSION;
if ((is_abstract && is_final) ||
(is_interface && !is_abstract) ||
(is_interface && major_gte_15 && (is_super || is_enum)) ||
(!is_interface && major_gte_15 && is_annotation)) {
ResourceMark rm(THREAD);
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbolHandles::java_lang_ClassFormatError(),
"Illegal class modifiers in class %s: 0x%X",
_class_name->as_C_string(), flags
);
return;
}
}
其验证规则如下:
- 如果该类是抽象同时又是final的,则修饰符错误
- 如果该类是接口同时又不是抽象的,则修饰符错误
- 如果该类是接口,同时该类为(枚举或者含有JVM_ACC_SUPER访问标记),则修饰符错误
- 如果该类是注解,但不含有接口标记,则修饰符错误
验证当前类索引
这点在JAVA虚拟机规范的说明如下:
类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。
这里涉及的代码如下:
// 读取当前类的索引
u2 this_class_index = cfs->get_u2_fast();
// 验证在常量池的指定索引处,其类型为CONSTANT_Class_info,验证索引值是有限的
check_property(
valid_cp_range(this_class_index, cp_size) &&
cp->tag_at(this_class_index).is_unresolved_klass(),
"Invalid this class index %u in constant pool in class file %s",
this_class_index, CHECK_(nullHandle));
//
symbolHandle class_name (THREAD, cp->unresolved_klass_at(this_class_index));
assert(class_name.not_null(), "class_name can't be null");
// It's important to set parsed_name *before* resolving the super class.
// (it's used for cleanup by the caller if parsing fails)
// 在解析父类之前保存parsed_name 是很重要的,如果解析失败的话,用来使调用者进行清理的
parsed_name = class_name;
// Update _class_name which could be null previously to be class_name 修改_class_name
_class_name = class_name;
// Don't need to check whether this class name is legal or not.
// It has been checked when constant pool is parsed.
// However, make sure it is not an array type.
// 不需要检查类名是否合格.它已经在常量池解析的时候检查过了.但是,需要检查它不是数组类型
if (_need_verify) {
guarantee_property(class_name->byte_at(0) != JVM_SIGNATURE_ARRAY,
"Bad class name in class file %s",
CHECK_(nullHandle));
}
klassOop preserve_this_klass; // for storing result across HandleMark 通过HandleMark来存储结构
// release all handles when parsing is done 当解析完之后释放所有的handle
{ HandleMark hm(THREAD); // 定义在handles.hpp 中
// Checks if name in class file matches requested name
// 检查在类文件中名字是否和要求的名字匹配
if (name.not_null() && class_name() != name()) {
ResourceMark rm(THREAD);
// 如果不匹配,则抛出java.lang.NoClassDefFoundError
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbolHandles::java_lang_NoClassDefFoundError(),
"%s (wrong name: %s)",
name->as_C_string(),
class_name->as_C_string()
);
return nullHandle;
}
if (TraceClassLoadingPreorder) {
tty->print("[Loading %s", name()->as_klass_external_name());
if (cfs->source() != NULL) tty->print(" from %s", cfs->source());
tty->print_cr("]");
}
这里的代码比较简单,就不在展开了
解析父类索引
关于父类索引,jvm规范如下:
父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的access_flag中都不能带有ACC_FINAL标记。对于接口来说,它的Class文件的super_class项的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object的CONSTANT_Class_info类型常量。如果Class文件的super_class的值为0,那这个Class文件只可能是定义的是java.lang.Object类,只有它是唯一没有父类的类。
这里涉及的代码如下:
// 6. 读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄
u2 super_class_index = cfs->get_u2_fast();
if (super_class_index == 0) {
// 6.1 如果是为0的话,则检查其类名是否是java.lang.Object
check_property(class_name() == vmSymbols::java_lang_Object(),
"Invalid superclass index %u in class file %s",
super_class_index,
CHECK_(nullHandle));
} else {
// 6.2 检查其对应的类型为klass
check_property(valid_cp_range(super_class_index, cp_size) &&
is_klass_reference(cp, super_class_index),
"Invalid superclass index %u in class file %s",
super_class_index,
CHECK_(nullHandle));
// The class name should be legal because it is checked when parsing constant pool.
// However, make sure it is not an array type.
// 检查其类型不是数组
bool is_array = false;
if (cp->tag_at(super_class_index).is_klass()) {
super_klass = instanceKlassHandle(THREAD, cp->resolved_klass_at(super_class_index));
if (_need_verify)
is_array = super_klass->oop_is_array();
} else if (_need_verify) {
is_array = (cp->unresolved_klass_at(super_class_index)->byte_at(0) == JVM_SIGNATURE_ARRAY);
}
if (_need_verify) {
// 如果是数组,则抛出异常
guarantee_property(!is_array,
"Bad superclass name in class file %s", CHECK_(nullHandle));
}
}
同样,代码很简单,这里就不再展开了