这里再贴一下, loadRawClass 方法的步骤:
- 打开对应的class文件
- 加载版本号
- 加载常量池
- 加载类的标识符 <— 本文讲解该步骤.
- 加载接口 <— 本文讲解该步骤.
- 加载字段
- 加载方法
- 加载类的属性
- 关闭流
加载类的标识符
在jvm规范中,支持的access_flags,如图:
- 带有ACC_SYNTHETIC标志的类,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。
- 带有ACC_ENUM标志的类,意味着它或它的父类被声明为枚举类型。
- 带有ACC_INTERFACE标志的类,意味着它是接口而不是类,反之是类而不是接口。如果一个Class文件被设置了ACC_INTERFACE标志,那么同时也得设置ACC_ABSTRACT标志(JLS §9.1.1.1)。同时它不能再设置ACC_FINAL、ACC_SUPER 和 ACC_ENUM标志。
- 注解类型必定带有ACC_ANNOTATION标记,如果设置了ANNOTATION标记,ACC_INTERFACE也必须被同时设置。如果没有同时设置ACC_INTERFACE标记,那么这个Class文件可以具有表4.1中的除ACC_ANNOTATION外的所有其它标记。当然ACC_FINAL和ACC_ABSTRACT这类互斥的标记除外(JLS §8.1.1.2)。
- ACC_SUPER标志用于确定该Class文件里面的invokespecial指令使用的是哪一种执行语义。目前Java虚拟机的编译器都应当设置这个标志。ACC_SUPER标记是为了向后兼容旧编译器编译的Class文件而存在的,在JDK1.0.2版本以前的编译器产生的Class文件中,access_flag里面没有ACC_SUPER标志。同时,JDK1.0.2前的Java虚拟机遇到ACC_SUPER标记会自动忽略它。
- 没有使用的access_flags标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为0, Java虚拟机实现也会自动忽略它们。
而在kvm中识别的access_flag为ACC_PUBLIC , ACC_FINAL ,ACC_SUPER , ACC_INTERFACE ACC_ABSTRACT.
这部分涉及的代码为:
static void
loadClassInfo(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass)
{
INSTANCE_CLASS thisClass;
INSTANCE_CLASS superClass;
CONSTANTPOOL ConstantPool = CurrentClass->constPool;
// 1. 读取accessFlag
unsigned short accessFlags = loadShort(ClassFileH) & RECOGNIZED_CLASS_FLAGS;
#if INCLUDEDEBUGCODE
if (traceclassloadingverbose) {
fprintf(stdout, "Loading class info\n");
}
#endif /* INCLUDEDEBUGCODE */
// 2. 验证accessFlag 是否正确
verifyClassFlags(accessFlags);
{
// 3. 读取this_class,并进行验证
unsigned short thisClassIndex = loadShort(ClassFileH);
verifyConstantPoolEntry(CurrentClass, thisClassIndex, CONSTANT_Class);
thisClass = (INSTANCE_CLASS)CP_ENTRY(thisClassIndex).clazz;
// 如果不想等的话,则抛出异常
if (CurrentClass != thisClass) {
/*
* JVM Spec 5.3.5:
*
* Otherwise, if the purported representation does not actually
* represent a class named N, loading throws an instance of
* NoClassDefFoundError or an instance of one of its
* subclasses.
*/
raiseException(NoClassDefFoundError);
}
}
{
// 4. 读取super_class,并进行验证
unsigned short superClassIndex = loadShort(ClassFileH);
if (superClassIndex == 0) {
superClass = NULL;
} else {
verifyConstantPoolEntry(CurrentClass, superClassIndex,
CONSTANT_Class);
superClass = (INSTANCE_CLASS)CP_ENTRY(superClassIndex).clazz;
}
}
CurrentClass->superClass = superClass;
CurrentClass->clazz.accessFlags = accessFlags;
#if INCLUDEDEBUGCODE
if (traceclassloadingverbose)
fprintf(stdout, "Class info loaded ok\n");
#endif /* INCLUDEDEBUGCODE */
}
这里的步骤如下:
- 读取accessFlag
- 验证accessFlag 是否正确
- 读取this_class,并进行验证
- 读取super_class,并进行验证
其中我们重点看第2步,代码如下:
static void
verifyClassFlags(unsigned short flags)
{
if (flags & ACC_INTERFACE) {
if ((flags & ACC_ABSTRACT) == 0)
goto failed;
if (flags & ACC_FINAL)
goto failed;
} else {
if ((flags & ACC_FINAL) && (flags & ACC_ABSTRACT))
goto failed;
}
return;
failed:
raiseExceptionWithMessage(ClassFormatError, KVM_MSG_BAD_CLASS_ACCESS_FLAGS);
}
- 如果设置了ACC_INTERFACE,但是没有设置ACC_ABSTRACT则抛出异常
- 如果设置了ACC_INTERFACE,但是设置了ACC_FINAL则抛出异常
- 其他情况,如果同时设置了ACC_FINAL,ACC_ABSTRACT,则抛出异常
加载接口
class中文件涉及到接口的为:
u2 interfaces_count;
u2 interfaces[interfaces_count];
-
interfaces_count
为接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。
-
interfaces[]
接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量(§4.4.1),其中0 ≤ i < interfaces_count。在interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。
在kvm中,这里涉及的代码为:
static void
loadInterfaces(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass)
{
unsigned short interfaceCount = loadShort(ClassFileH);
long byteSize = (interfaceCount+1) * sizeof(unsigned short);
unsigned int ifIndex;
#if INCLUDEDEBUGCODE
if (traceclassloadingverbose) {
fprintf(stdout, "Loading interfaces\n");
}
#endif /* INCLUDEDEBUGCODE */
if (interfaceCount == 0) {
return;
}
// 1. 分配内存
if (USESTATIC) {
CurrentClass->ifaceTable = (unsigned short *)mallocBytes(byteSize);
} else {
long cellSize = ByteSizeToCellSize(byteSize);
CurrentClass->ifaceTable =
(unsigned short*)callocPermanentObject(cellSize);
}
/* 在ifaceTable 下标为0的位置,保存接口的数量 */
CurrentClass->ifaceTable[0] = interfaceCount;
// 2. 复制数据
for (ifIndex = 1; ifIndex <= interfaceCount; ifIndex++) {
unsigned short cpIndex = loadShort(ClassFileH);
verifyConstantPoolEntry(CurrentClass, cpIndex, CONSTANT_Class);
CurrentClass->ifaceTable[ifIndex] = cpIndex;
}
#if INCLUDEDEBUGCODE
if (traceclassloadingverbose) {
fprintf(stdout, "Interfaces loaded ok\n");
}
#endif /* INCLUDEDEBUGCODE */
}
这里的步骤为:
- 分配内存,在ifaceTable 下标为0的位置,保存接口的数量
- 复制数据