在字节码文件中,常量池的字节码流所在的块区紧跟在魔数和版本号之后,因此JVM在解析完魔数与版本号后就开始解析常量池。JVM解析Java类字节码文件的接口:ClassFileParser::parseClassFile(),总体步骤如下:解析魔数–>解析版本号–>解析常量池–>解析父类–>解析接口–>解析类变量–>解析类方法–>构建类结构。JVM解析常量池信息主要链路:ClassFileParser::parseClassFile()–>ClassFileParser::parse_constant_pool()–>oopFactory::new_constantPool()(分配常量池内存);ClassFileParser::parse_constant_pool_entries()(解析常量池信息)。
onstantPoolOop constant_pool = oopFactory::new——constantPool(length,oopDesc::IsSafeConc,CHECK_(nullHandle));
oopFactory::new_constantPool()的链路比较长,下图展示了总体调用路径:
perm指内存的永久保存区域,这一部分用于存放Class和Meta的信息,Class在被加载的时候放入permGen space区域,如果加载了太多类就很可能出现PermGen space错误。而在metaSpace时代,这块区域是属于“本地内存”区域,也就是操作系统,相对于JVM虚拟机内存而言的,JVM直接向操作系统申请内存存储Java类的袁茜茜,如此一来可以解决为permGen space指定的内存太小而导致perm区内存耗尽的问题。
从宏观层面上看,常量池内存分配大体上可以分为以下三步:
从oopFactory::new_constantPool()调用开始,到mutableSpace:allocate()的过程可认为是第一阶段,即为constantPool申请内存。
JVM为constantPool申请内存链路
内存申请最终通过objec_space()->allocate(word_size)实现,定义如下:
HeapWord* MutableSpace::allocate(size_t size) {
assert(Heap_lock->owned_by_self() ||
(SafepointSynchronize::is_at_safepoint() &&
Thread::current()->is_VM_thread()),
"not locked");
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {
HeapWord* new_top = obj + size;
set_top(new_top);
assert(is_object_aligned((intptr_t)obj) && is_object_aligned((intptr_t)new_top),
"checking alignment");
return obj;
} else {
return NULL;
}
}
为一个Java类创建其对应的常量池,需要在JVM堆区为常量池先申请一块连续空间,所申请的空间大小取决于一个Java类在编译时所确定的常量池大小(size),在常量池初始化链路中调用constantPoolKlass::allocate()方法,这个方法会调用constantPoolOopDesc::object_size(length)方法获取常量池大小,方法原型:
static int header_size() { return sizeof(constantPoolOopDesc)/HeapWordSize; }
static int object_size(int length) { return align_object_size(header_size() + length); }
object_size()的逻辑十分简单,就是将header_size和length相加后进行内存对齐。header_size()返回的是constantPoolOopDesc类型的大小。align_object_size()是实现内存对齐,便于GC进行工作时更加高效。在32位操作系统上,HeapWordSize大小为4,是一个指针变量的长度,在32位平台上sizeof(constantPoolOopDesc)返回40.
当计算C++类时,返回的是其所有变量的大小加上虚函数指针大小,若在类中定义了普通函数,都不会计算其大小。
#include
class A {
private:
char* a;
public:
int getA() {
return 1;
}
};
int main() {
printf("sizeof(A)=%d\n", sizeof(A));
return 0;
}
这一段程序在32位系统输出是4,64位系统输出为8,因为他只包含一个char型指针变量。如此一来我们可以推算sizeof(constantPoolOopDesc)的返回值大小
constantPoolOopDesc本身包含8个字段
而由于constantPoolOopDesc继承自oopDesc类,因此还会包含父类的成员变量:
volatile markOop _mark; //指针
union _metadata{ //指针
wideKlassOop _klass;
narrowOop _compressed_klass;
}_metadata;
const int HeapWordSize = sizeof(HeapWord);
class HeapWord {
friend class VMStructs;
private:
char* i;
#ifndef PRODUCT
public:
char* value() { return i; }
#endif
};
可以看到,HeapWordSize就是一个char*类型的大小,在32位平台是4,64位平台是8,所以return sizeof(constantPoolOopDesc)/HeapWordSize;返回的就是constantPoolOopDesc类型本身所占内存的总字节数。所以最终constantPoolKlass::allocate()从JVM堆中所申请的内存空间大小包含:constantPoolOopDesc大小和Java类常量池元素数量。
我们来举一个例子:
public class Iphone6S {
int length = 138;
int width = 67;
int height = 7;
int weight = 142;
int ram = 2;
int rom = 16;
int pixel = 1200;
}
字节码中显示常量池大小为0x0023,对应十进制35,因此常量池一共包含38个元素,再看javap反编译的信息:
Constant pool:
#1 = Methodref #10.#25 // java/lang/Object."":()V
#2 = Fieldref #9.#26 // JVM/Iphone6S.length:I
#3 = Fieldref #9.#27 // JVM/Iphone6S.width:I
#4 = Fieldref #9.#28 // JVM/Iphone6S.height:I
#5 = Fieldref #9.#29 // JVM/Iphone6S.weight:I
#6 = Fieldref #9.#30 // JVM/Iphone6S.ram:I
#7 = Fieldref #9.#31 // JVM/Iphone6S.rom:I
#8 = Fieldref #9.#32 // JVM/Iphone6S.pixel:I
#9 = Class #33 // JVM/Iphone6S
#10 = Class #34 // java/lang/Object
#11 = Utf8 length
#12 = Utf8 I
#13 = Utf8 width
#14 = Utf8 height
#15 = Utf8 weight
#16 = Utf8 ram
#17 = Utf8 rom
#18 = Utf8 pixel
#19 = Utf8
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 SourceFile
#24 = Utf8 Iphone6S.java
#25 = NameAndType #19:#20 // "":()V
#26 = NameAndType #11:#12 // length:I
#27 = NameAndType #13:#12 // width:I
#28 = NameAndType #14:#12 // height:I
#29 = NameAndType #15:#12 // weight:I
#30 = NameAndType #16:#12 // ram:I
#31 = NameAndType #17:#12 // rom:I
#32 = NameAndType #18:#12 // pixel:I
#33 = Utf8 JVM/Iphone6S
#34 = Utf8 java/lang/Object
JVM会保留0号常量池位置,所以只有34个元素。按照上面分析,JVM会从permSpace中划分(40+35)*4字节的内存大小(32位平台)。
JVM为常量池对象申请的内存位于perm区,perm区本事是一片连续的内存区域,而JVM为常量池申请内存时也是正片区域连续划分,因此每一个constantPoolOop对象实例在perm区中都是连续分布的,不会存在碎片化。
最终申请好的空间布局如图ConstantPoolOop内存布局.png所示(假设运行于Linux32位平台,指针宽度为4字节,并且常量池分配方向从低地址到高地址。JVM内部为对象分配内存时,先分配对象头,然后分配对象的实例数据,不管字段对象还是方法对象亦或是数组,都是如此。
JVM为Java类所对应的常量分配好内存空间后,接着需要对这段内存空间进行初始化(实际上是清零操作)。在Linux32位平台上最终调用pd_fill_to_words()函数,声明如下:
static void pd_fill_to_words(HeapWord* tohw, size_t count, juint value) {
#ifdef AMD64
julong* to = (julong*) tohw;
julong v = ((julong) value << 32) | value;
while (count-- > 0) {
*to++ = v;
}
#else
juint* to = (juint*)tohw;
count *= HeapWordSize / BytesPerInt;
while (count-- > 0) {
*to++ = value;
}
#endif // AMD64
}
可以看到这个函数有3个入参,会将指定内存区的内存数据全部清空为value值,在CollectedHeap::init_obj()中调用了Copy::fill_to_aligned_words(obj+hs,size-hs)函数,而Copy::fill_to_aligned_words()函数有三个入参,声明如下:
static void fill_to_aligned_words(HeapWord* to, size_t count, juint value = 0) {
assert_params_aligned(to);
pd_fill_to_aligned_words(to, count, value);
}
该函数第三个参数默认为0,所以在执行pd_fill_to_aligned_words()函数时,指定的内存区会全部清零。
JVM内部基于oop-klass模型描述一个Java类,将一个Java类一拆为二,分别描述,第一个模型是oop,第二个模型是klass。所谓oop,实际上是指ordinary object pointer(普通对象指针),用来表示对象的实例信息,看起来像个指针,实际上对象实例数据都藏在指针所指向的内存首地址后面的一片内存区域中。而klass则包含元数据和方法信息,用来描述Java类或者JVM内部自带的C++类型信息,Java类的继承信息、成员变量、静态变量、成员方法、构造函数等信息都在klass中保存。JVM由此便可以在运行期反射出Java类的全部结构信息。
tip:Java类的所有函数都视为是“virtual”的,这样Java类的每个方法都可以直接被其子类覆盖而不需要添加任何关键字作为修饰符,因此,Java类中的每个方法都可以晚绑定,而也正是因为所有函数都视为虚函数,所以在JVM内部的C++就必须维护一套函数分发表。
handle是对oop的行为的封装,在访问Java类时,一定是通过handle内部指针得到oop示例的,在通过oop就能拿到klass,如此handle最终便能操纵oop的行为了。
Handle类的基本结构:
class Handle VALUE_OBJ_CLASS_SPEC {
private:
oop* _handle;
//省略部分代码
};
可以看到Handle内部只有一个成员变量_handle指向oop类型,因此该变量指向的是一个oop首地址。oop一般由对象头、对象专有属性和数据体三部分构成,结构如图
oop模型.png
JVM内部定义了若干oop类型,每一种oop类型都有自己特有的数据结构,oop的专有属性区便是用于存放各个oop所特有的数据结构的地方。
究竟什么是普通对象指针?
Hotspot里的oop其实就是GC所托管的指针,所有oopDesc及其子类(除了markOopDesc外)的实例都是由GC所管理
在HotSpot里面,oop就是指一个真正的指针,而markOop则是一个看起来像指针,实际上是藏在指针里面的对象(数据),并没有指向内存的功能,这也正是它不受GC托管的原因,只要除了函数作用域,指针变量就会直接从堆栈上释放,不需要垃圾回收了。
oop的继承体系:
typedef class oopDesc* oop; //所有oop顶级父类
typedef class instanceOopDesc* instanceOop; //表示Java类实例
typedef class methodOopDesc* methodOop; //表示Java方法
typedef class constMethodOopDesc* constMethodOop; //表示Java方法中的只读信息(字节码指令)
typedef class methodDataOopDesc* methodDataOop; //表示性能统计的相关数据
typedef class arrayOopDesc* arrayOop; //表示数组对象
typedef class objArrayOopDesc* objArrayOop; //表示引用类型数组对象
typedef class typeArrayOopDesc* typeArrayOop; //表示基本类型数组对象
typedef class constantPoolOopDesc* constantPoolOop; //表示Java字节码文件中的常量池
typedef class constantPoolCacheOopDesc* constantPoolCacheOop; //与constantPoolOop伴生,是后者的缓存对象
typedef class klassOopDesc* klassOop; //指向JVM内部的klass实例的对象
typedef class markOopDesc* markOop; //oop的标记对象
typedef class compiledICHolderOopDesc* compiledICHolderOop;
klass的继承体系:
class Klass; //klass家族基类
class instanceKlass; //虚拟机层面上与Java类对等的数据结构
class instanceMirrorKlass; //描述java.lang.Class的实例
class instanceRefKlass; //描述java.lang.ref.Reference的子类
class methodKlass; //表示Java类的方法
class constMethodKlass; //描述Java类方法所对应的字节码指令信息的固有属性
class methodDataKlass;
class klassKlass; //klass链路末端,在jdk8中已经不存在
class instanceKlassKlass;
class arrayKlassKlass;
class objArrayKlassKlass;
class typeArrayKlassKlass;
class arrayKlass; //描述Java数组的信息,是抽象基类
class objArrayKlass; //描述Java引用类型数组的数据结构
class typeArrayKlass; //描述Java中基本类型数组的数据结构
class constantPoolKlass; //描述Java字节码文件中的常量池的数据结构
class constantPoolCacheKlass;
class compiledICHolderKlass;
基类klass定义:
class Klass : public Klass_vtbl {
friend class VMStructs;
protected:
enum { _primary_super_limit = 8 };
jint _layout_helper;
juint _super_check_offset;
Symbol* _name;
public:
oop* oop_block_beg() const { return adr_secondary_super_cache(); }
oop* oop_block_end() const { return adr_next_sibling() + 1; }
protected:
klassOop _secondary_super_cache;
objArrayOop _secondary_supers;
klassOop _primary_supers[_primary_super_limit];
oop _java_mirror;
klassOop _super;
klassOop _subklass;
klassOop _next_sibling;
//
// End of the oop block.
//
jint _modifier_flags; // Processed access flags, for use by Class.getModifiers.
AccessFlags _access_flags; // Access flags. The class/interface distinction is stored here.
juint _alloc_count; // allocation profiling support - update klass_size_in_bytes() if moved/deleted
// Biased locking implementation and statistics
// (the 64-bit chunk goes first, to avoid some fragmentation)
jlong _last_biased_lock_bulk_revocation_time;
markOop _prototype_header; // Used when biased locking is both enabled and disabled for this type
jint _biased_lock_revocation_count;
//省略部分代码
};
字段名 | 含义 |
---|---|
_layout_helper | 对象布局的综合描述符 |
_name | 类名,如java/lang/String |
_java_mirror | 类的镜像类 |
_super | 父类 |
_subklass | 指向第一个子类 |
_next_sibling | 指向下一个兄弟结点 |
_modifier_flags | 修饰符标识,如static |
_access_flags | 访问权限标识,如public |
handle通过oop拿到klass,可以说是对普通对象的一种间接引用,这完全是为GC考虑
在C++领域,类的继承和多态性最终通过vptr(虚函数表)来实现,在klass内部记录了每一个类的vptr信息
存放Java类中非静态和非private方法入口,jVM调用Java类的方法(非static和非private)时,最终访问vtable找到对应入口
存放java类所实现的接口类方法,JVM调用接口方法时,最终访问itable找到对应入口。
vtable和itable并不存放Java类方法和接口方法的直接入口,而是指向了Method对象入口。
handle体系家族:
// Specific Handles for different oop types oop家族对应的handle宏定义
#define DEF_HANDLE(type, is_a) \
class type##Handle; \
class type##Handle: public Handle { \
protected: \
type##Oop obj() const { return (type##Oop)Handle::obj(); } \
type##Oop non_null_obj() const { return (type##Oop)Handle::non_null_obj(); } \
\
public: \
/* Constructors */ \
type##Handle () : Handle() {} \
type##Handle (type##Oop obj) : Handle((oop)obj) { \
assert(SharedSkipVerify || is_null() || ((oop)obj)->is_a(), \
"illegal type"); \
} \
type##Handle (Thread* thread, type##Oop obj) : Handle(thread, (oop)obj) { \
assert(SharedSkipVerify || is_null() || ((oop)obj)->is_a(), "illegal type"); \
} \
\
/* Special constructor, use sparingly */ \
type##Handle (type##Oop *handle, bool dummy) : Handle((oop*)handle, dummy) {} \
\
/* Operators for ease of use */ \
type##Oop operator () () const { return obj(); } \
type##Oop operator -> () const { return non_null_obj(); } \
};
DEF_HANDLE(instance , is_instance )
DEF_HANDLE(method , is_method )
DEF_HANDLE(constMethod , is_constMethod )
DEF_HANDLE(methodData , is_methodData )
DEF_HANDLE(array , is_array )
DEF_HANDLE(constantPool , is_constantPool )
DEF_HANDLE(constantPoolCache, is_constantPoolCache)
DEF_HANDLE(objArray , is_objArray )
DEF_HANDLE(typeArray , is_typeArray )
// Specific KlassHandles for different Klass types klass家族对应的handle宏定义
#define DEF_KLASS_HANDLE(type, is_a) \
class type##Handle : public KlassHandle { \
public: \
/* Constructors */ \
type##Handle () : KlassHandle() {} \
type##Handle (klassOop obj) : KlassHandle(obj) { \
assert(SharedSkipVerify || is_null() || obj->klass_part()->is_a(), \
"illegal type"); \
} \
type##Handle (Thread* thread, klassOop obj) : KlassHandle(thread, obj) { \
assert(SharedSkipVerify || is_null() || obj->klass_part()->is_a(), \
"illegal type"); \
} \
\
/* Access to klass part */ \
type* operator -> () const { return (type*)obj()->klass_part(); } \
\
static type##Handle cast(KlassHandle h) { return type##Handle(h()); } \
\
};
DEF_KLASS_HANDLE(instanceKlass , oop_is_instance_slow )
DEF_KLASS_HANDLE(methodKlass , oop_is_method )
DEF_KLASS_HANDLE(constMethodKlass , oop_is_constMethod )
DEF_KLASS_HANDLE(klassKlass , oop_is_klass )
DEF_KLASS_HANDLE(arrayKlassKlass , oop_is_arrayKlass )
DEF_KLASS_HANDLE(objArrayKlassKlass , oop_is_objArrayKlass )
DEF_KLASS_HANDLE(typeArrayKlassKlass , oop_is_typeArrayKlass)
DEF_KLASS_HANDLE(arrayKlass , oop_is_array )
DEF_KLASS_HANDLE(typeArrayKlass , oop_is_typeArray_slow)
DEF_KLASS_HANDLE(objArrayKlass , oop_is_objArray_slow )
DEF_KLASS_HANDLE(constantPoolKlass , oop_is_constantPool )
DEF_KLASS_HANDLE(constantPoolCacheKlass, oop_is_constantPool )
JVM内部,为了让GC便于回收(既能回收一个类实例所对应的实例数据oop,也能回收其对应的元数据和需方法表klass[一个是堆区的垃圾回收,一个是永久区的垃圾回收]),将oop本身鞥装成了oop,而klass也被封装成了oop。
handle主要用于封装组昂oop和klass,因此往往在声明handle类实例的时候,直接将oop或者klass传递进去完成封装。在JVM执行Java类方法时,最终也是通过handle拿到对应的oop和klass,为了高效快速调用,JVM重载了类方法访问操作符->。
Handle的构造函数:
inline Handle::Handle(oop obj) {
if (obj == NULL) {
_handle = NULL;
} else {
_handle = Thread::current()->handle_area()->allocate_handle(obj);
}
}
这个构造函数接收oop类型参数,并将其保存到当前线程在堆区申请的handleArea表中,由于oop仅是一种指针,所以表中存储的实际上也是指针。
默认构造函数:
// Constructors
Handle() { _handle = NULL; }
Handle重载操作符->:
oop non_null_obj() const { assert(_handle != NULL, "resolving NULL handle"); return *_handle; }
oop operator -> () const { return non_null_obj(); }
治理定义了oop operate ->()操作符重载函数,返回non_null_obj(),而后者直接返回oop类型的*_handle类型,因此,如果JVM想要调用oop的某个函数,可以直接通过handle。
klass体系的Handle类全部继承与KlassHandle这个基类,与oop体系类似,KlassHandle中也定义了多种构造函数用来实现对klass或oop的封装,并且重载了operate ->()函数实现从handle直接调用klass的函数。JVM创建Java类所对应的类模型时便使用了这种方式。
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
Handle class_loader,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS){
// We can now create the basic klassOop for this klass
klassOop ik = oopFactory::new_instanceKlass(name, vtable_size, itable_size,
static_field_size,
total_oop_map_count,
rt, CHECK_(nullHandle));
instanceKlassHandle this_klass (THREAD, ik);
// Fill in information already parsed
this_klass->set_access_flags(access_flags);
this_klass->set_should_verify_class(verify);
jint lh = Klass::instance_layout_helper(instance_size, false);
this_klass->set_layout_helper(lh);
assert(this_klass->oop_is_instance(), "layout is correct");
assert(this_klass->size_helper() == instance_size, "correct size_helper");
// Not yet: supers are done below to support the new subtype-checking fields
//this_klass->set_super(super_klass());
this_klass->set_class_loader(class_loader());
this_klass->set_nonstatic_field_size(nonstatic_field_size);
this_klass->set_has_nonstatic_fields(has_nonstatic_fields);
}
在该函数中,先通过klassOop ik = oopFactory::new_instanceKlass();创建了一个klassOopDesc实例,接着通过instanceKlassHandle this_klass (THREAD, ik); 将oop封装到了instanceKlassHandle中,接下来便通过this_klass指针来直接调用instanceKlass中的各种函数。
为了便于GC回收,每一种klass实例最终都要被封装成对应的oop,具体操作是先分配对应的oop实例,然后将klass实例分配到oop对象头后面,从而实现oop+klass这种内存布局结构。对于任何一种给定的oop和其对应的klass,oop对象首地址到其对应的klass对象首地址的距离是固定的,因此只要得到一个便能根据相对寻址找出另一个。对于每一种oop,都提供了klass_part()函数,可以直接由oop得到对应的klass实例。
klass_part()原型:
Klass* klass_part() const { return (Klass*)((address)this + sizeof(klassOopDesc)); }
同样,将klass转换为oop只需要对klass首地址做减法
假设在某个Java方法中连续实例化了三个Student类,那么JVM内存中会出现如下图的布局:
到目前为止,JVM为constantPoolOop所分配的内存区域还是空的,但是,在ClassFileParser:parse_constant_pool()函数中执行完oopFactory::new_constantPool()函数时,已经为constantPoolOop初始化好了_metadata所指向的实例。
constantPoolOop oopFactory::new_constantPool(int length,
bool is_conc_safe,
TRAPS) {
constantPoolKlass* ck = constantPoolKlass::cast(Universe::constantPoolKlassObj());
return ck->allocate(length, is_conc_safe, CHECK_NULL);
}
constantPoolKlass* ck = constantPoolKlass::cast(Universe::constantPoolKlassObj());这行代码调用全局对象Universe的静态函数constantPoolKlassObj()来获取constantPoolKlass实例指针,逻辑如下:
static klassOop constantPoolKlassObj(){
return _constatntPoolKlassObj;
}
这个函数直接返回了全局静态变量_constantPoolKlassObj,该变量在JVM启动过程中被实例化,JVM实例化过程会调用如下逻辑:
klassOop constantPoolKlass::create_klass(TRAPS) {
constantPoolKlass o;
KlassHandle h_this_klass(THREAD, Universe::klassKlassObj());
KlassHandle k = base_create_klass(h_this_klass, header_size(), o.vtbl_value(), CHECK_NULL);
// Make sure size calculation is right
assert(k()->size() == align_object_size(header_size()), "wrong size for object");
java_lang_Class::create_mirror(k, CHECK_NULL); // Allocate mirror
return k();
}
这个函数获取了klassKlass实例,JVM初始化过程中会执行一下逻辑:
klassOop klassKlass::create_klass(TRAPS) {
KlassHandle h_this_klass;
klassKlass o;
// for bootstrapping, handles may not be available yet.
klassOop k = base_create_klass_oop(h_this_klass, header_size(), o.vtbl_value(), CHECK_NULL);
k->set_klass(k); // point to thyself
// Do not try to allocate mirror, java.lang.Class not loaded at this point.
// See Universe::fixup_mirrors()
return k;
}
这个逻辑中最终调用了base_create_klass_oop()函数创建klassKlass实例,该实例是全局性的,可以通过Universe::klassKlassObj()获取
至此,JVM启动过程中,先创建了klassKlass实例,在根据该实例创建了常量池对应的Klass类——constantPoolKlass。所以我们先要分析klassKlass实例的创建。
从进入klassKlass.cpp::create_klass()之后就一路开始调用层层封装的函数,直到CollectedHeap::permanent_obj_allocate_no_klass_install()才结束,这里开始在永久区为obj分配对象内存,这个函数的逻辑如下:
HeapWord* CollectedHeap::common_mem_allocate_init(size_t size, TRAPS) {
HeapWord* obj = common_mem_allocate_noinit(size, CHECK_NULL);
init_obj(obj, size);
return obj;
}
当这个函数执行完毕后,JVM的永久区便多了一个对象内存布局,该对象是klassKlassOop,由oop对象头(_mark和_metadata)和klassKlass实例区组成,而现在_metadata指向哪里呢?
为klassOop分配完内存后,接下来便是将这段内存清零,之所以要清零,是因为JVM永久区是一个可以重复使用的内存区域,会被GC反复回收,因此刚刚申请的内存区域可能还保存着原来已经被清理的对象的数据。
经过上面两个步骤,接下来就要向内存填充数据了,oop一共包含两个成员变量:_mark和_metadata,这里先填充mark变量,主要通过调用post_allocate_setup_no_klass_install函数实现,主要调用了obj->set_mark(markOopDesc::prototype())函数。声明如下:
static markOop prototype() {
return markOop( no_hash_in_place | no_lock_in_place );
}
在C语言中,有一种快速赋初值的写法,例如
int x = 3;
//可以写成
int x(3);
//同样对于指针数据:
int x = 3;
int *p = &x;
//可以写成:
int x = 3;
int *p(&x);
markOop的实际类型是markOopDesc*类型,同样支持上面的写法,所以这个函数最终将返回一个指针,存储了一个整型数字,由于oop._mark成员变量用于存储JVM内部对象的哈希值,线程锁等信息,而此时返回的mark表示则标记当前oop尚未建立唯一哈希值。JVM内部每次读取oop的mark标识时会调用markOopDesc的value函数,定义如下:
uintptr_t value() const { return (uintptr_t) this; }
可以看到,内部其实是将this指针当做值使用,并没有指向markOopDesc实例,它仅仅用于存储JVM内部对象的哈希值、锁状态标识等信息。
初始化玩mark标记后,开始初始化_metadata成员,这是一个联合体,最终是NULL
现在已经完成了oop对象头初始化,接着开始初始化klassKlass类型实例Klass::base_create_klass_oop,经过这一步后大部分属性值都被赋空。
执行完上面步骤后,返回到了klassKlass::create_klass()函数中,开始执行k->set_klass(k)这个逻辑,这里的k是klassOop,是klassOopDesc*类型指针,这一步其实是在设置klassOopDesc的_metadata变量,在之前的步骤中,_metadata被设置为NULL,而这一次则是将它指向了自己。
constantPoolKlass模型构建的过程与上文相同:
虽然常量池模型构建与klassKlass构建的基本逻辑一致,但是入参不同,而最后_metadata所指也不同,constantPoolKlass对应的oop的_metadata最终指向前面创建的klass,最终内存布局如下图:
其实constantPoolKlass正式constantPoolOop的类元信息,即constantPoolOop的_metadata指针应该指向constantPoolKlass对象实例,而JVM内部确实也这么做了:
constantPoolOop oopFactory::new_constantPool(int length,
bool is_conc_safe,
TRAPS) {
constantPoolKlass* ck = constantPoolKlass::cast(Universe::constantPoolKlassObj());
return ck->allocate(length, is_conc_safe, CHECK_NULL);
}
在这段逻辑中先是获取到了随JVM启动创建的constantPoolKlass实例指针ck,然后调用了ck->allocate()方法,ck指针最终被传递到post_allocation_install_obj_klass()中,因此JVM在执行obj->set_klass()方法时,constantPoolOop的_metadata指针便在此处指向了constantPoolKlass实例对象,最终构建的constantPoolOop内存布局如下图:
至此constantPoolOopDesc实例的构建便结束了,其他oop实例对象的构建大同小异。
JVM在构建constantPoolOop过程中,由于它本身大小不固定,所以需要使用constantPoolKlass来描述不固定信息,而由于constantPoolKlass也是不固定的,因此会引用klassKlass来描述自己,但如果klassKlass也引用一个klass来描述自身,那么klass链路将一直持续,因此JVM将klassKlass作为整个引用链的终结符,并让他自己指向自己。
在构建constantPoolOop的过程中,会执行constantPoolKlass::allocate()函数,该函数干了三件事:
constantPoolOop constantPoolKlass::allocate(int length, bool is_conc_safe, TRAPS) {
//省略部分代码
pool->set_length(length);
pool->set_tags(NULL);
pool->set_cache(NULL);
pool->set_operands(NULL);
pool->set_pool_holder(NULL);
pool->set_flags(0);
// only set to non-zero if constant pool is merged by RedefineClasses
pool->set_orig_length(0);
// if constant pool may change during RedefineClasses, it is created
// unsafe for GC concurrent processing.
pool->set_is_conc_safe(is_conc_safe);
// all fields are initialized; needed for GC
//省略部分代码
}
在创建constantPoolOop过程中,会为其_tags域申请内存空间:
// initialize tag array
typeArrayOop t_oop = oopFactory::new_permanent_byteArray(length, CHECK_NULL);
typeArrayHandle tags (THREAD, t_oop);
for (int index = 0; index < length; index++) {
tags()->byte_at_put(index, JVM_CONSTANT_Invalid);
}
pool->set_tags(tags());
可以看到JVM在永久区为tags开辟了与constantPoolOop实例数据长度一致的空间,接下来JVM开始解析字节码文件的常量池元素,并逐个填充到这块区域。
现在开始解析常量池元素,为此专门定义了一个函数: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().
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.
const char* names[SymbolTable::symbol_alloc_batch_size];
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;
// parsing Index 0 is unused
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.
u1 tag = cfs->get_u1_fast(); //从字节码文件中读取占1字节宽度的字节流,因为每一个常量池的起始元素的第一个字节用于描述常量池元素类型
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);//将当前常量池元素的类型和名称索引分别保存到constantPoolOop的tag数组和数据区
}
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;
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;
//省略部分代码
}
cp->klass_index_at_put(index, name_index);是将当前常量池元素保存到constantPoolOop中,其实现方式如下:
void klass_index_at_put(int which, int name_index) {
tag_at_put(which, JVM_CONSTANT_ClassIndex); //将当前常量池元素的类型保存到constantPoolOop所指向的tag对应位置的数组中
*int_at_addr(which) = name_index; //将当前常量池元素的名称索引保存到constantPoolOop的数据区中对应的位置
}
void tag_at_put(int which, jbyte t) { tags()->byte_at_put(which, t); }
当前常量池元素本身在字节码文件常量区位置索引将决定该元素最终在tag数组中的位置,也决定它的索引值最终在constantPoolOop的数据区的位置。
Constant pool:
#1 = Methodref #10.#25 // java/lang/Object."":()V
#2 = Fieldref #9.#26 // JVM/Iphone6S.length:I
#3 = Fieldref #9.#27 // JVM/Iphone6S.width:I
#4 = Fieldref #9.#28 // JVM/Iphone6S.height:I
#5 = Fieldref #9.#29 // JVM/Iphone6S.weight:I
#6 = Fieldref #9.#30 // JVM/Iphone6S.ram:I
#7 = Fieldref #9.#31 // JVM/Iphone6S.rom:I
#8 = Fieldref #9.#32 // JVM/Iphone6S.pixel:I
#9 = Class #33 // JVM/Iphone6S
#10 = Class #34 // java/lang/Object
#11 = Utf8 length
#12 = Utf8 I
#13 = Utf8 width
#14 = Utf8 height
#15 = Utf8 weight
#16 = Utf8 ram
#17 = Utf8 rom
#18 = Utf8 pixel
#19 = Utf8
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 SourceFile
#24 = Utf8 Iphone6S.java
#25 = NameAndType #19:#20 // "":()V
#26 = NameAndType #11:#12 // length:I
#27 = NameAndType #13:#12 // width:I
#28 = NameAndType #14:#12 // height:I
#29 = NameAndType #15:#12 // weight:I
#30 = NameAndType #16:#12 // ram:I
#31 = NameAndType #17:#12 // rom:I
#32 = NameAndType #18:#12 // pixel:I
#33 = Utf8 JVM/Iphone6S
#34 = Utf8 java/lang/Object
enum {
JVM_CONSTANT_Utf8 = 1,
JVM_CONSTANT_Unicode, /* unused */
JVM_CONSTANT_Integer,
JVM_CONSTANT_Float,
JVM_CONSTANT_Long,
JVM_CONSTANT_Double,
JVM_CONSTANT_Class,
JVM_CONSTANT_String,
JVM_CONSTANT_Fieldref,
JVM_CONSTANT_Methodref,
JVM_CONSTANT_InterfaceMethodref,
JVM_CONSTANT_NameAndType,
JVM_CONSTANT_MethodHandle = 15, // JSR 292
JVM_CONSTANT_MethodType = 16, // JSR 292
//JVM_CONSTANT_(unused) = 17, // JSR 292 early drafts only
JVM_CONSTANT_InvokeDynamic = 18, // JSR 292
JVM_CONSTANT_ExternalMax = 18 // Last tag found in classfiles
};
我们仍然拿上面的字节码作为例子,第一个常量池元素为Methodref,因此tags的#1位置存的值是10,而constantPoolOop的数据区的#1位置有两个索引怎么存储呢?JVM对两个索引值进行了拼接,变成一个值然后存储:
void interface_method_at_put(int which, int class_index, int name_and_type_index) {
tag_at_put(which, JVM_CONSTANT_InterfaceMethodref);
*int_at_addr(which) = ((jint) name_and_type_index<<16) | class_index; // Not so nice
}
因此,#10.#25在constantPoolOop的数据区的#1位置实际存的是:0x0A19
无论是tag还是constantPoolOop的数据区,一个存储位置只能存放一个指针宽度的数据,而字符串往往很大,因此JVM专门设计了一个符号表的内存区,tag和constantPoolOop数据区内仅存指针指向符号区:
case JVM_CONSTANT_Utf8 :
{
cfs->guarantee_more(2, CHECK); // utf8_length
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
if (_need_verify) {
verify_legal_utf8((unsigned char*)utf8_buffer, utf8_length, CHECK);
}
if (EnableInvokeDynamic && 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;
Symbol* result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash);
if (result == NULL) {
names[names_count] = (char*)utf8_buffer;
lengths[names_count] = utf8_length;
indices[names_count] = index;
hashValues[names_count++] = hash;
if (names_count == SymbolTable::symbol_alloc_batch_size) {
SymbolTable::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
names_count = 0;
}
} else {
cp->symbol_at_put(index, result);
}
}
break;
为了节省内存JVM会先判断符号表中是否存在相同的字符串,如果已经存在则不会分配内存,这就是你在一个类中定义两个字符串但是这两个字符串的值相同,最终都会同时指向常量池中同一个位置的原因。