我们在上篇文章介绍了创建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 ASSERTassert(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 = 8int 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_flagsu2 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_flagsu2 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 ASSERTassert(cfs0->current() == old_current, "non-exclusive use of stream()");
#endifcfs0->set_current(cfs1.current());
}

可以看到,该方法共有5个步骤:

  1. 使用classfilestream的本地副本。它有助于C++编译器优化这个函数(可以在寄存器中分配_current,用标量替换集合)。当函数返回时,当前指针将被复制回stream()。不要在此方法中调用使用stream()的其他方法。
  2. 进行符号批量的分配
  3. 依次解析常量池元素
  4. 分配剩余的符号
  5. 设置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_flagsu2 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_flagsu2 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_flagsu2 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_flagsu2 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_flagsu4 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_flagscfs->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 lockMutexLocker 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 lockMutexLocker 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");
}

其步骤如下:

  1. 尝试进行分配,在UseConcMarkSweepGC和UseParallelGC 不会进行分配的.这里的分配是指首先在堆中分配symbolOop.将结果保存在之前声明的sym_oops中.

  2. 如果分配失败的话,则换种方式进行分配,直接插入到符号表中

  3. 如果在第1步成功的话.则此时需要将symbol添加到符号表中

接下来,我们只介绍第1步情况,第2步和第1步流程类似.

其涉及的代码如下:

bool symbolKlass::allocate_symbols(int names_count, const char** names,int* lengths, symbolOop* sym_oops, TRAPS) {// 1. 如果是cms 或 ParallelGC 则直接返回falseif (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 则直接返回falseif (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)

类文件解析004-解析常量池元素相关推荐

  1. 类文件解析003-解析常量池

    本文我们来介绍ClassFileParser 解析常量池的过程.解析常量池的过程是在ClassFileParser::parseClassFile 通过parse_constant_pool 来实现的 ...

  2. 《深入理解Java虚拟机》——类文件结构之魔数常量池

    相对于Java虚拟机的其他部分,这部分的内容我们只需要搞清楚下面两个方面的内容: 1.无关性 2.Class文件的结构与组成 我们都知道Java有个特性是:一次编写,到处运行.这里体现的是平台无关性, ...

  3. class字节码文件中的常量池结构详解

    文章目录 前言 方法区 常量池基本结构 JVM 所定义的11种常量 常量池元素的复合结构 常量池的结束位置 常量池元素总数量 第一个常量池元素 父类常量 变量型常量池元素 自己的学习笔记,部分节选自& ...

  4. 【深入理解JVM】Java类文件的基本结构

    Java类文件(.class文件)是一个为已编译Java程序仔细定义的格式.Java源代码被编译成能够被任何JVM加载和执行的类文件.在被JVM加载之前,类文件可能是由网络传输而来. 类文件是独立于底 ...

  5. java基础(八) 深入解析常量池与装拆箱机制

    ###引言 本文将介绍常量池 与 装箱拆箱机制,之所以将两者合在一起介绍,是因为网上不少文章在谈到常量池时,将包装类的缓存机制,java常量池,不加区别地混在一起讨论,更有甚者完全将这两者视为一个整体 ...

  6. 深入解析常量池与装拆箱机制

    2019独角兽企业重金招聘Python工程师标准>>> 常量池 常量 可分为 字面常量(也称为直接常量)和 符号常量. 字面常量: 是指在程序中无需预先定义就可使用的数字.字符.bo ...

  7. Java常量池解析与字符串intern简介

    在Java应用程序运行时,Java虚拟机会保存一份内部的运行时常量池,它区别于class文件的常量池,是class文件常量池映射到虚拟机中的数据结构. 关于class文件常量池的部分可以参考之前的博文 ...

  8. java怎编写么解析一个类型_DAY3:你必须知道的java虚拟机之类篇——类文件的结构...

    马上过年啦,不知道大家今年有没有投资基金股票呢?是赚的盆满钵满还是拍断大腿,可以评论区一起交流交流,秀一秀哈哈,反正我是没来得及上车. 暴富西不可能暴富的啦,打工人嘛几能写写文章啦-记得点赞➕关注呀 ...

  9. java 字符串池 原理_《Java虚拟机原理图解》1.2.2、Class文件中的常量池详解(上)...

    注意: 对于某个类而言,其class文件中至少要有两个CONSTANT_Class_info常量池项,用来表示自己的类信息和其父类信息.(除了java.lang.Object类除外,其他的任何类都会默 ...

  10. stringbuilder调用tostring常量池_彻底弄懂java中的常量池

    作者:tracy_666链接:https://www.jianshu.com/p/55f65dac1b4b JVM常量池主要分为Class文件常量池.运行时常量池,全局字符串常量池,以及基本类型包装类 ...

最新文章

  1. 8086为什么不用c语言,现代汇编教材还是基于8086,对理解当今CPU(如i9)有帮助吗,还是教程太滞...
  2. python求平方根的代码_python如何求平方根
  3. 同步 GIT@OSC 实现MARKDOWN文件发布或更新到CSDN博客中
  4. python打包库_Python 打包自己的库到 PYPI (可pip安装)
  5. 根据网页地址获取页面内容
  6. SpringBoot + Mybatis-puls + ClickHouse增删改查入门教程
  7. [设计模式-行为型]解释器模式(Interpreter)
  8. Oracle 无备份情况下的恢复--密码文件/参数文件
  9. 机器学习5-支持向量机
  10. 华南理工大学811信号与系统真题
  11. 黑魔法-伪类匹配列表数目实现微信群头像CSS布局的技巧
  12. 【分享】 [教学]破解完全入门篇
  13. @Reference是干啥的
  14. 监控服务器网卡上传和下载网速的脚本
  15. Linux系统进程的理解与相关命令的使用系列
  16. 德云社怒怼信息泄露 300元买600多明星证件号
  17. 大学生计算机适合用苹果笔记本吗,2019学生党笔记本推荐 苹果笔记本适合大学生吗...
  18. 不用发论文也不教课的12名教授,为这个研究所拿下8次学界最高奖
  19. 基于WebKit的网络爬虫
  20. 进程调度的时机与过程

热门文章

  1. 如何在PowerPoint中显示,隐藏或调整幻灯片缩略图的大小
  2. 前端纯CSS导入otf字体包
  3. aardio匹配问题以及编码问题
  4. 未来5年互联网,运营将成比产品更加有前途的职业?
  5. 天使轮,种子,A轮,B轮,C轮,Pre-IPO
  6. iOS描述文件(.mobileprovision)一键申请
  7. 关于 PHP 的框架 phalcon 学习(一) url 路由过程。
  8. matlab 直流无刷电机,无刷直流电机的matlab仿真.pdf
  9. LINUX中ECHO命令的使用
  10. ​NeurIPS 2022 | IPMT:用于小样本语义分割的中间原型挖掘Transformer