我们在上篇文章介绍了创建constantPoolOop的过程,本文我们就来看看解析常量池元素的过程.这里涉及的方法为:ClassFileParser::parse_constant_pool_entries。代码如下:
void ClassFileParser::parse_constant_pool_entries(constantPoolHandle cp, int length, TRAPS) {
// Use a local copy of ClassFileStream. It helps the C++ compiler to optimize
// this function (_current can be allocated in a register, with scalar
// replacement of aggregates). The _current pointer is copied back to
// stream() when this function returns. DON'T call another method within
// this method that uses stream().
// 1.使用classfilestream的本地副本。它有助于C++编译器优化这个函数(可以在寄存器中分配_current,用标量替换集合)。当函数返回时,当前指针将被复制回stream()。不要在此方法中调用使用stream()的其他方法。
ClassFileStream* cfs0 = stream();
ClassFileStream cfs1 = *cfs0;
ClassFileStream* cfs = &cfs1;
#ifdef ASSERT
assert(cfs->allocated_on_stack(),"should be local");
u1* old_current = cfs0->current();
#endif
// Used for batching symbol allocations.
// 2. 进行符号批量的分配
const char* names[SymbolTable::symbol_alloc_batch_size]; //SymbolTable::symbol_alloc_batch_size = 8
int lengths[SymbolTable::symbol_alloc_batch_size];
int indices[SymbolTable::symbol_alloc_batch_size];
unsigned int hashValues[SymbolTable::symbol_alloc_batch_size];
int names_count = 0;
// 3.parsing Index 0 is unused 解析, 下标0是没使用的
for (int index = 1; index < length; index++) {
// Each of the following case guarantees one more byte in the stream
// for the following tag or the access_flags following constant pool,
// so we don't need bounds-check for reading tag.
// 以下每种情况都保证流中的以下标记或常量池后面的访问标记有一个以上的字节,因此我们不需要对读取标记进行边界检查
// 3.1 读取标记
u1 tag = cfs->get_u1_fast();
switch (tag) {
case JVM_CONSTANT_Class :
{
cfs->guarantee_more(3, CHECK); // name_index, tag/access_flags
u2 name_index = cfs->get_u2_fast();
cp->klass_index_at_put(index, name_index);
}
break;
case JVM_CONSTANT_Fieldref :
{
cfs->guarantee_more(5, CHECK); // class_index, name_and_type_index, tag/access_flags
u2 class_index = cfs->get_u2_fast();
u2 name_and_type_index = cfs->get_u2_fast();
cp->field_at_put(index, class_index, name_and_type_index);
}
break;
.... 省略
default:
classfile_parse_error(
"Unknown constant tag %u in class file %s", tag, CHECK);
break;
}
}
// Allocate the remaining symbols 分配剩余的符号
if (names_count > 0) {
oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
}
// Copy _current pointer of local copy back to stream().
#ifdef ASSERT
assert(cfs0->current() == old_current, "non-exclusive use of stream()");
#endif
cfs0->set_current(cfs1.current());
}
可以看到,该方法共有5个步骤:
- 使用classfilestream的本地副本。它有助于C++编译器优化这个函数(可以在寄存器中分配_current,用标量替换集合)。当函数返回时,当前指针将被复制回stream()。不要在此方法中调用使用stream()的其他方法。
- 进行符号批量的分配
- 依次解析常量池元素
- 分配剩余的符号
- 设置classfilestream的内部指针
这里需要介绍一下符号的批量分配,这里涉及的常量池的元素为CONSTANT_Utf8_info.由于在处理该元素的时候,会在符号表中新建符号,为了提高效率,因此在此处使用批处理的方式,分批处理.
常量池元素解析
在解析时,是通过循环处理的,其下标是从1开始的.其原因如下:
constant_pool表的索引值只有在大于0且小于constant_pool_count时才会被认为是有效的,对于long和double类型有例外情况。虽然值为0的constant_pool索引是无效的,但其他用到常量池的数据结构可以使用索引0来表示“不引用任何一个常量池项”的意思
解析JVM_CONSTANT_Class
CONSTANT_Class_info结构用于表示类或接口,格式如下:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_Class_info结构的项的说明:
- tag CONSTANT_Class_info结构的tag项的值为CONSTANT_Class
- name_index name_index项的值,必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,代表一个有效的类或接口二进制名称的内部形式。
因为数组也是由对象表示,所以字节码指令anewarray和multianewarray可以通过常量池中的CONSTANT_Class_info(§4.4.1)结构来引用类数组。对于这些数组,类的名字就是数组类型的描述符,例如:
- 表示int二维数组类型 int[][] 的名字是: [[I
- 表示一维Thread数组类型 Thread[]的名字是: [Ljava/lang/Thread;
一个有效的数组类型描述符中描述的数组维度必须小于等于255。
这里涉及的代码为:
u1 tag = cfs->get_u1_fast();
case JVM_CONSTANT_Class :
{
cfs->guarantee_more(3, CHECK); // name_index, tag/access_flags
u2 name_index = cfs->get_u2_fast();
cp->klass_index_at_put(index, name_index);
}
break;
在case JVM_CONSTANT_Class: 中,调用了cfs->guarantee_more(3, CHECK) 方法保证在读取了tag类型后, classfilestream 还有3个字节可读. 这3个字节的内容可能为:
- name_index + 下一个常量池元素的tag
- name_index + access_flags 的第1个字节.此情况只在该元素是最后一个常量池元素时发生
读取到name_index后,调用cp->klass_index_at_put(index, name_index) 进行赋值.其代码如下:
// 在hotspot/src/share/vm/oops/constantPoolOop.hpp
void klass_index_at_put(int which, int name_index) {
tag_at_put(which, JVM_CONSTANT_ClassIndex);
*int_at_addr(which) = name_index;
}
首先看第1步:设置tag数组的值,代码如下:
// 在hotspot/src/share/vm/oops/constantPoolOop.hpp
void tag_at_put(int which, jbyte t) { tags()->byte_at_put(which, t); }
//hotspot/src/share/vm/oops/typeArrayOop.hpp
void byte_at_put(int which, jbyte contents) { *byte_at_addr(which) = contents; }
其最终会在constantPoolOop 所持有的tags()中下标为index的元素赋值为101(JVM_CONSTANT_ClassIndex的值为101).当然,这个值是临时的
接着看第2步: *int_at_addr(which) = name_index;其代码如下:
jint* int_at_addr(int which) const {
assert(is_within_bounds(which), "index out of bounds");
return (jint*) &base()[which];
}
intptr_t* base() const { return (intptr_t*) (((char*) this) + sizeof(constantPoolOopDesc)); }
其最终会在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存name_index.
解析JVM_CONSTANT_Fieldref JVM_CONSTANT_Methodref JVM_CONSTANT_InterfaceMethodref
字段,方法和接口方法有类似的结构表示:
字段:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
方法:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
接口方法:
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
这些结构各项的说明如下:
-
tag
CONSTANT_Fieldref_info结构的tag项的值为CONSTANT_Fieldref(9)。 CONSTANT_Methodref_info结构的tag项的值为CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info结构的tag项的值为 CONSTANT_InterfaceMethodref(11)。
-
class_index
class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。 CONSTANT_Methodref_info结构的class_index项的类型必须是类(不能是接口)。CONSTANT_InterfaceMethodref_info结构的class_index项的类型必须是接口(不能是类)。CONSTANT_Fieldref_info结构的class_index项的类型既可以是类也可以是接口
-
name_and_type_index
name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。
在一个CONSTANT_Fieldref_info结构中,给定的描述符必须是字段描述符(§4.3.2)。而CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info中给定的描述符必须是方法描述符(§4.3.3)。
如果一个CONSTANT_Methodref_info结构的方法名以“<”(’\u003c’)开头,则说明这个方法名是特殊的<init>,即这个方法是实例初始化方法(§2.9),它的返回类型必须为空。
这里涉及的代码为:
u1 tag = cfs->get_u1_fast();
case JVM_CONSTANT_Fieldref :
{
cfs->guarantee_more(5, CHECK); // class_index, name_and_type_index, tag/access_flags
u2 class_index = cfs->get_u2_fast();
u2 name_and_type_index = cfs->get_u2_fast();
cp->field_at_put(index, class_index, name_and_type_index);
}
break;
case JVM_CONSTANT_Methodref :
{
cfs->guarantee_more(5, CHECK); // class_index, name_and_type_index, tag/access_flags
u2 class_index = cfs->get_u2_fast();
u2 name_and_type_index = cfs->get_u2_fast();
cp->method_at_put(index, class_index, name_and_type_index);
}
break;
case JVM_CONSTANT_InterfaceMethodref :
{
cfs->guarantee_more(5, CHECK); // class_index, name_and_type_index, tag/access_flags
u2 class_index = cfs->get_u2_fast();
u2 name_and_type_index = cfs->get_u2_fast();
cp->interface_method_at_put(index, class_index, name_and_type_index);
}
break;
在这三种情况中中,调用了cfs->guarantee_more(5, CHECK) 方法保证在读取了tag类型后, classfilestream 还有5个字节可读. 这5个字节的内容可能为:
- class_index + name_and_type_index + 下一个常量池元素的tag
- class_index + name_and_type_index + access_flags 的第1个字节.此情况只在该元素是最后一个常量池元素时发生
在处理JVM_CONSTANT_Fieldref时,会调用 cp->field_at_put(index, class_index, name_and_type_index)进行赋值,其代码如下:
void field_at_put(int which, int class_index, int name_and_type_index) {
tag_at_put(which, JVM_CONSTANT_Fieldref);
*int_at_addr(which) = ((jint) name_and_type_index<<16) | class_index;
}
可见:其最终会constantPoolOop 所持有的tags()中下标为index的元素赋值为9(JVM_CONSTANT_Fieldref的值为9).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存name_index和name_and_type_index(经过压缩).
对于JVM_CONSTANT_Methodref, JVM_CONSTANT_InterfaceMethodref 和 JVM_CONSTANT_Fieldref 类似,这里就不再展开了.
解析JVM_CONSTANT_Integer
CONSTANT_Intrger_info 表示Int的数值类型.其结构如下:
CONSTANT\_Integer\_info {
u1 tag;
u4 bytes;
}
-
tag
CONSTANT_Integer_info结构的tag项的值是CONSTANT_Integer(3)。
-
bytes
CONSTANT_Intrger_info结构的bytes项表示int常量的值,按照Big-Endian的顺序存储。
涉及的代码如下:
u1 tag = cfs->get_u1_fast();
case JVM_CONSTANT_Integer :
{
cfs->guarantee_more(5, CHECK); // bytes, tag/access_flags
u4 bytes = cfs->get_u4_fast();
cp->int_at_put(index, (jint) bytes);
}
break;
这里首先通过 get_u4_fast()获取 int常量的值.注意,这里进行了字节的转换.具体细节在类加载流程-002 中有叙述,这里就不在展开了.
最后调用cp->int_at_put(index, (jint) bytes) 进行处理.代码如下:
void int_at_put(int which, jint i) {
tag_at_put(which, JVM_CONSTANT_Integer);
*int_at_addr(which) = i;
}
可见,其最终是在constantPoolOop 所持有的tags()中下标为index的元素赋值为3(JVM_CONSTANT_Integer的值为3).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存Int值.
对于JVM_CONSTANT_Float,JVM_CONSTANT_Long,JVM_CONSTANT_Double 来说,其处理过程和CONSTANT_Intrger_info类似,区别在于,JVM_CONSTANT_Long,JVM_CONSTANT_Double 其对应的值为8个字节,因此在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存值的时候,需要占用8个字节.代码如下:
cp->long_at_put(index, bytes);
void long_at_put(int which, jlong l) {
tag_at_put(which, JVM_CONSTANT_Long);
// *long_at_addr(which) = l;
Bytes::put_native_u8((address)long_at_addr(which), *((u8*) &l));
}
解析JVM_CONSTANT_Utf8
CONSTANT_Utf8_info结构用于表示字符串常量的值:
CONSTANT\_Utf8\_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info结构各项的说明如下:
-
tag
CONSTANT_Utf8_info结构的tag项的值为CONSTANT_Utf8(1)。
-
length
length项的值指明了bytes[]数组的长度(注意,不能等同于当前结构所表示的String对象的长度),CONSTANT_Utf8_info结构中的内容是以length属性确定长度而不是以null作为字符串的终结符。
-
bytes[]
bytes[]是表示字符串值的byte数组,bytes[]数组中每个成员的byte值都不会是0,也不在0xf0至0xff范围内。
这里涉及的代码为:
u1 tag = cfs->get_u1_fast();
case JVM_CONSTANT_Utf8 :
{
cfs->guarantee_more(2, CHECK); // utf8_length
// 1. 读取字符串长度
u2 utf8_length = cfs->get_u2_fast();
u1* utf8_buffer = cfs->get_u1_buffer();
assert(utf8_buffer != NULL, "null utf8 buffer");
// Got utf8 string, guarantee utf8_length+1 bytes, set stream position forward.
cfs->guarantee_more(utf8_length+1, CHECK); // utf8 string, tag/access_flags
cfs->skip_u1_fast(utf8_length);
// Before storing the symbol, make sure it's legal
// 2. 如果需要进行验证的话,则验证其格式是否正确
if (_need_verify) {
verify_legal_utf8((unsigned char*)utf8_buffer, utf8_length, CHECK);
}
if (AnonymousClasses && has_cp_patch_at(index)) {
Handle patch = clear_cp_patch_at(index);
guarantee_property(java_lang_String::is_instance(patch()),
"Illegal utf8 patch at %d in class file %s",
index, CHECK);
char* str = java_lang_String::as_utf8_string(patch());
// (could use java_lang_String::as_symbol instead, but might as well batch them)
utf8_buffer = (u1*) str;
utf8_length = (int) strlen(str);
}
unsigned int hash;
// 3. 从符号表中查找该字符串是否存在
symbolOop result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash);
if (result == NULL) {// 4.1 如果符号表中没有,添加到容器中
names[names_count] = (char*)utf8_buffer;
lengths[names_count] = utf8_length;
indices[names_count] = index;
hashValues[names_count++] = hash;
// 4.2 如果容器的size 达到阈值, 则真正的进行批处理,向符号表进行添加
if (names_count == SymbolTable::symbol_alloc_batch_size) {
oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
names_count = 0;
}
} else {
// 5. 如果该符号存在的话,则直接复用
cp->symbol_at_put(index, result);
}
}
break;
// 6. 如果解析完常量池元素后,仍有剩余,则再次进行批处理,向符号表进行添加
if (names_count > 0) {
oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
}
此处我们重点来看一下 oopFactory::new_symbols方法.其代码如下:
// 位于hotspot/src/share/vm/memory/oopFactory.hpp
static void new_symbols(constantPoolHandle cp, int names_count,
const char** name, int* lengths,
int* cp_indices, unsigned int* hashValues,
TRAPS) {
SymbolTable::add(cp, names_count, name, lengths, cp_indices,
hashValues, CHECK);
}
调用:
void SymbolTable::add(constantPoolHandle cp, int names_count,
const char** names, int* lengths, int* cp_indices,
unsigned int* hashValues, TRAPS) {
symbolKlass* sk = (symbolKlass*) Universe::symbolKlassObj()->klass_part();
symbolOop sym_oops[symbol_alloc_batch_size];
// 1. 尝试进行分配,在UseConcMarkSweepGC和UseParallelGC 不会进行分配的
bool allocated = sk->allocate_symbols(names_count, names, lengths,
sym_oops, CHECK);
if (!allocated) {
// do it the hard way 2. 如果分配失败的话,则换种方式进行分配
for (int i=0; i<names_count; i++) {
assert(!Universe::heap()->is_in_reserved(names[i]) || GC_locker::is_active(),
"proposed name of symbol must be stable");
// We assume that lookup() has been called already, that it failed,
// and symbol was not found. We create the symbol here.
symbolKlass* sk = (symbolKlass*) Universe::symbolKlassObj()->klass_part();
symbolOop s_oop = sk->allocate_symbol((u1*)names[i], lengths[i], CHECK);
symbolHandle sym (THREAD, s_oop);
// Allocation must be done before grabbing the SymbolTable_lock lock
MutexLocker ml(SymbolTable_lock, THREAD);
SymbolTable* table = the_table();
int index = table->hash_to_index(hashValues[i]);
symbolOop s = table->basic_add(sym, index, (u1*)names[i], lengths[i],
hashValues[i], CHECK);
cp->symbol_at_put(cp_indices[i], s);
}
return;
}
// 3. 如果在第一步成功的话.则此时需要将symbol添加到符号表中
symbolHandle syms[symbol_alloc_batch_size];
for (int i=0; i<names_count; i++) {
syms[i] = symbolHandle(THREAD, sym_oops[i]);
}
// Allocation must be done before grabbing the SymbolTable_lock lock
MutexLocker ml(SymbolTable_lock, THREAD);
SymbolTable* table = the_table();
bool added = table->basic_add(syms, cp, names_count, names, lengths,
cp_indices, hashValues, CHECK);
assert(added, "should always return true");
}
其步骤如下:
-
尝试进行分配,在UseConcMarkSweepGC和UseParallelGC 不会进行分配的.这里的分配是指首先在堆中分配symbolOop.将结果保存在之前声明的sym_oops中.
-
如果分配失败的话,则换种方式进行分配,直接插入到符号表中
-
如果在第1步成功的话.则此时需要将symbol添加到符号表中
接下来,我们只介绍第1步情况,第2步和第1步流程类似.
其涉及的代码如下:
bool symbolKlass::allocate_symbols(int names_count, const char** names,
int* lengths, symbolOop* sym_oops, TRAPS) {
// 1. 如果是cms 或 ParallelGC 则直接返回false
if (UseConcMarkSweepGC || UseParallelGC) {
return false;
}
assert(names_count > 0, "can't allocate 0 symbols");
// 2. 计算每个符号需要分配的大小,并计算需要统一分配的大小
int total_size = 0;
int i, sizes[SymbolTable::symbol_alloc_batch_size];
for (i=0; i<names_count; i++) {
int len = lengths[i];
// 2.1 如果其符号的长度大于 (1 << 16) -1 则直接返回false
if (len > symbolOopDesc::max_length()) {
return false;
}
int sz = symbolOopDesc::object_size(len);
sizes[i] = sz * HeapWordSize;
total_size += sz;
}
// 3. 在堆上进行分配
symbolKlassHandle h_k(THREAD, as_klassOop());
HeapWord* base = Universe::heap()->permanent_mem_allocate(total_size);
if (base == NULL) {
return false;
}
// CAN'T take any safepoint during the initialization of the symbol oops !
// 在初始化symbol oop 中的过程中不能得到任何的安全点
No_Safepoint_Verifier nosafepoint;
// 4. 实例化数据,并保存到容器中
klassOop sk = h_k();
int pos = 0;
for (i=0; i<names_count; i++) {
symbolOop s = (symbolOop) (((char*)base) + pos);
s->set_mark(markOopDesc::prototype());
s->set_klass(sk);
s->set_utf8_length(lengths[i]);
const char* name = names[i];
for (int j=0; j<lengths[i]; j++) {
s->byte_at_put(j, name[j]);
}
assert(s->is_parsable(), "should be parsable here.");
sym_oops[i] = s;
pos += sizes[i];
}
return true;
}
这里的代码比较简单,接着我们看第三步即可.其最终调用SymbolTable::basic_add.代码如下:
bool SymbolTable::basic_add(symbolHandle* syms,
constantPoolHandle cp, int names_count,
const char** names, int* lengths,
int* cp_indices, unsigned int* hashValues,
TRAPS) {
// Cannot hit a safepoint in this function because the "this" pointer can move.
No_Safepoint_Verifier nsv;
for (int i=0; i<names_count; i++) {
assert(syms[i]->equals(names[i], lengths[i]), "symbol must be properly initialized");
// 1. 获得符号对应的hash
unsigned int hashValue;
if (use_alternate_hashcode()) {
hashValue = hash_symbol(names[i], lengths[i]);
} else {
hashValue = hashValues[i];
}
// 2. 根据hash找到对应的index,并进行查找
int index = hash_to_index(hashValue);
symbolOop test = lookup(index, names[i], lengths[i], hashValue);
if (test != NULL) {
// 2.1 如果找到的话,则直接复用即可
cp->symbol_at_put(cp_indices[i], test);
} else {
// 2.2 如果没有找到的话,则进行添加,然后添加到constantPool 中
symbolOop sym = syms[i]();
HashtableEntry* entry = new_entry(hashValue, sym);
add_entry(index, entry);
cp->symbol_at_put(cp_indices[i], sym);
}
}
return true; // always returns true
}
这里关于符号表的细节,在类加载流程004 中介绍.
接下来让我们看一下cp->symbol_at_put(cp_indices[i], sym).代码如下:
void symbol_at_put(int which, symbolOop s) {
tag_at_put(which, JVM_CONSTANT_Utf8);
oop_store_without_check(obj_at_addr(which), oop(s));
}
可见: 其最终是在constantPoolOop 所持有的tags()中下标为index的元素赋值为1(JVM_CONSTANT_Utf8的值为1).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存symbolOop.
总结
关于常量池其他元素,由于比较简单,就不再展开.下面用一个表格来总结一下:
常量池类型 | constantPoolOop 中保存的值 | tags 中保存的值 |
JVM_CONSTANT_Class | 保存name_index | 101(JVM_CONSTANT_ClassIndex的值为101) 该值为临时 |
JVM_CONSTANT_Fieldref | ((jint) name_and_type_index<<16) | class_index | 9(JVM_CONSTANT_Fieldref的值为9) |
JVM_CONSTANT_Methodref | ((jint) name_and_type_index<<16) | class_index | 10(JVM_CONSTANT_Methodref的值为10) |
JVM_CONSTANT_InterfaceMethodref | ((jint) name_and_type_index<<16) | class_index | 11(JVM_CONSTANT_InterfaceMethodref的值为11) |
JVM_CONSTANT_String | string_index | 103(JVM_CONSTANT_StringIndex的值为103) 此值为临时 |
JVM_CONSTANT_MethodHandle | ((jint) ref_index<<16) | ref_kind | 15(JVM_CONSTANT_MethodHandle的值为15) |
JVM_CONSTANT_MethodType | ref_index | 16(JVM_CONSTANT_MethodType的值为16) |
JVM_CONSTANT_InvokeDynamicTrans | ((jint) name_and_type_index<<16) | bootstrap_method_index | 17(JVM_CONSTANT_InvokeDynamicTrans的值为17) |
JVM_CONSTANT_InvokeDynamic | ((jint) name_and_type_index<<16) | bootstrap_specifier_index | 18(JVM_CONSTANT_InvokeDynamic的值为18) |
JVM_CONSTANT_Integer | int 值 | 3(JVM_CONSTANT_Integer的值为3) |
JVM_CONSTANT_Float | float 值 | 4(JVM_CONSTANT_Float的值为4) |
JVM_CONSTANT_Long | long 值 占64位 | 5(JVM_CONSTANT_Long的值为5) |
JVM_CONSTANT_Double | ddouble 值 占64位 | 6(JVM_CONSTANT_Double的值为6) |
JVM_CONSTANT_NameAndType | ((jint) signature_index<<16) | name_index | 12(JVM_CONSTANT_NameAndType的值为12) |
JVM_CONSTANT_Utf8 | symbolOop | 1(JVM_CONSTANT_Utf8的值为1) |