HotSpot中对象是是通过对象模型来表示的,具体来说就是oop和klass来表示,oop代表了一个Java对象在堆内存中的表现形式。使用klass来表示Java类的信息。虚拟机创建Java对象时,会在按照klass所提供的类信息,在内存中创建一个oop对象,具体的内存可能是线程TLAB或堆空间。然后该oop对象的地址会赋给一个变量,这个变量可能在线程栈上或直接来自堆空间对此oop的引用。
一.对象模型
hotspot/src/share/vm/oops/oop.hpp
HotSpot内部oop的描述如下,它有一个标记头markOop,和一个_metadata联合体指向改对象的Klass。
class oopDesc {
private:
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
// Fast access to barrier set. Must be initialized.
static BarrierSet* _bs;
}
hotspot/src/share/vm/oops/markOop.hpp
标记头存储着该对象的Hash,GC年龄,锁状态线程ID等。
class markOopDesc: public oopDesc {
private:
// Conversion
uintptr_t value() const { return (uintptr_t) this; }
public:
//各标记所占位数
enum { age_bits = 4,
lock_bits = 2,
biased_lock_bits = 1,
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits,
cms_bits = LP64_ONLY(1) NOT_LP64(0),
epoch_bits = 2
};
//各标记偏移
enum { lock_shift = 0,
biased_lock_shift = lock_bits,
age_shift = lock_bits + biased_lock_bits,
cms_shift = age_shift + age_bits,
hash_shift = cms_shift + cms_bits,
epoch_shift = hash_shift
};
//计算标记位置
enum { lock_mask = right_n_bits(lock_bits),
lock_mask_in_place = lock_mask << lock_shift,
biased_lock_mask = right_n_bits(lock_bits + biased_lock_bits),
biased_lock_mask_in_place= biased_lock_mask << lock_shift,
biased_lock_bit_in_place = 1 << biased_lock_shift,
age_mask = right_n_bits(age_bits),
age_mask_in_place = age_mask << age_shift,
epoch_mask = right_n_bits(epoch_bits),
epoch_mask_in_place = epoch_mask << epoch_shift,
cms_mask = right_n_bits(cms_bits),
cms_mask_in_place = cms_mask << cms_shift
#ifndef _WIN64
,hash_mask = right_n_bits(hash_bits),
hash_mask_in_place = (address_word)hash_mask << hash_shift
#endif
};
//锁对应的值
enum { locked_value = 0,
unlocked_value = 1,
monitor_value = 2,
marked_value = 3,
biased_lock_pattern = 5
};
//对象hash
enum { no_hash = 0 }; // no hash value assigned
enum { no_hash_in_place = (address_word)no_hash << hash_shift,
no_lock_in_place = unlocked_value
};
//对象GC年龄
enum { max_age = age_mask };
enum { max_bias_epoch = epoch_mask };
hotspot/src/share/vm/oops/klass.hpp
Kalss中是这个对象的部分类描述信息
class Klass : public Metadata {
protected:
jint _layout_helper;
......
Klass* _super; //父类
Klass* _subklass; //子类
Klass* _next_sibling; //兄弟类
Klass* _next_link; //klass连
ClassLoaderData* _class_loader_data; //同一类加载器加载的类由它管理
jint _modifier_flags;
AccessFlags _access_flags; //类访问权限
.....
//虚表长度
int _vtable_len;
//跨代引用支持
jbyte _modified_oops; // Card Table Equivalent (YC/CMS support)
jbyte _accumulated_modified_oops; // Mod Union Equivalent (CMS support)
hotspot/src/share/vm/oops/instanceKlass.hpp
class InstanceKlass: public Klass {
//类状态
enum ClassState {
allocated, // allocated (but not yet linked)
loaded, // loaded and inserted in class hierarchy (but not linked yet)
linked, // successfully linked/verified (but not initialized yet)
being_initialized, // currently running class initializer
fully_initialized, // initialized (successfull final state)
initialization_error // error happened during initialization
};
//当前类的对象实例数量
static int number_of_instance_classes() { return _total_instanceKlass_count; }
private:
static volatile int _total_instanceKlass_count;
protected:
//类注解信息
Annotations* _annotations;
//类包信息
PackageEntry* _package_entry;
//
Klass* volatile _array_klasses;
//类的常量池
ConstantPool* _constants;
//内部类
Array<jushort>* _inner_classes;
const char* _source_debug_extension;
//符号信息
Symbol* _array_name;
//字段数量
int _nonstatic_field_size;
int _static_field_size; // number words used by static fields (oop and non-oop) in this klass
......
Array<u2>* _fields;
}
二.对象创建
在Java中创建一个对象时,通常会形如: new Object() ,这个动作翻译成字节码如下:
0: new #2(指向常量池类全限定名索引)
3: dup
4: invokespecial #3(类的<init>初始化方法)
7: astore_1
8: return
hotspot/src/cpu/x86/vm/templateTable_x86.cpp
在模板解释器一篇讲过每一条Java字节码都对应一个指令模板,new对应的指令模板如下,将访问常量池,查找类信息,不存在则走慢分配(slow_case), 存在则走如下分配流程
- 尝试线程局部分配缓存分配
- 失败,在Eden区分配
- 对象实例数据初始化
- 对象头的初始化
void TemplateTable::_new() {
transition(vtos, atos); //栈顶缓存验证
__ get_unsigned_2_byte_index_at_bcp(rdx, 1); //取new指令后的参数,从当前指令偏移1字节出取得,放入rdx
Label slow_case;
Label slow_case_no_pop;
Label done;
Label initialize_header;
Label initialize_object; // including clearing the fields
Label allocate_shared;
//取常量池tags首地址放入rax,常量池首地址放入rcx
__ get_cpool_and_tags(rcx, rax);
//确保class已在常量池中
const int tags_offset = Array<u1>::base_offset_in_bytes();
__ cmpb(Address(rax, rdx, Address::times_1, tags_offset), JVM_CONSTANT_Class);
__ jcc(Assembler::notEqual, slow_case_no_pop);
//获取InstanceKlass地址放入rcx
__ movptr(rcx, Address(rcx, rdx, Address::times_ptr, sizeof(ConstantPool)));
__ push(rcx); // save the contexts of klass for initializing the header
//是否解析过
__ cmpb(Address(rcx, InstanceKlass::init_state_offset()), InstanceKlass::fully_initialized);
__ jcc(Assembler::notEqual, slow_case);
// 取类实例大小,存入rdx寄存器
__ movl(rdx, Address(rcx, Klass::layout_helper_offset()));
// 是都有finalizer实现
__ testl(rdx, Klass::_lh_instance_slow_path_bit);
__ jcc(Assembler::notZero, slow_case);
//
// Allocate the instance
// 1) 尝试在 TLAB 扽配
// 2) 如果失败则到Eden区分配
// 3) 如果常量池中找不到这个类的Klass,则类还没加载,走slow_case,慢分配
// (creates a new TLAB, etc.)
//......
//尝试线程局部分配缓存分配
if (UseTLAB) {
// 获取TLAB区剩余空间首地址,放入rax寄存器。
__ movptr(rax, Address(thread, in_bytes(JavaThread::tlab_top_offset())));
//rdx寄存器已经记录了对象大小,此处及根据TLAB空闲区首地址,计算出对象分配后,对象尾地址,放入rbx中
__ lea(rbx, Address(rax, rdx, Address::times_1));
//判断空间是否足够
__ cmpptr(rbx, Address(thread, in_bytes(JavaThread::tlab_end_offset())));
__ jcc(Assembler::above, allow_shared_alloc ? allocate_shared : slow_case);
__ movptr(Address(thread, in_bytes(JavaThread::tlab_top_offset())), rbx);
//清零操作
if (ZeroTLAB) {
// the fields have been already cleared
__ jmp(initialize_header);
} else {
// initialize both the header and fields
__ jmp(initialize_object);
}
}
//在Eden取分配
//
// rdx: instance size in bytes
if (allow_shared_alloc) {
__ bind(allocate_shared);
//获取Eden区剩余空间的首地址和结束地址。
ExternalAddress heap_top((address)Universe::heap()->top_addr());
ExternalAddress heap_end((address)Universe::heap()->end_addr());
Label retry;
__ bind(retry);
__ movptr(rax, heap_top); //起始
__ lea(rbx, Address(rax, rdx, Address::times_1)); //计算分配后的起始地址
__ cmpptr(rbx, heap_end);
__ jcc(Assembler::above, slow_case);
//更新
// rax,: object begin
// rbx,: object end
// rdx: instance size in bytes
__ locked_cmpxchgptr(rbx, heap_top);
// if someone beat us on the allocation, try again, otherwise continue
__ jcc(Assembler::notEqual, retry);
__ incr_allocated_bytes(thread, rdx, 0);
}
//对象实例数据初始化
if (UseTLAB || Universe::heap()->supports_inline_contig_alloc()) {
// The object is initialized before the header. If the object size is
// zero, go directly to the header initialization.
__ bind(initialize_object);
__ decrement(rdx, sizeof(oopDesc));
__ jcc(Assembler::zero, initialize_header);
// Initialize topmost object field, divide rdx by 8, check if odd and
// test if zero.
__ xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
__ shrl(rdx, LogBytesPerLong); // divide by 2*oopSize and set carry flag if odd
// rdx must have been multiple of 8
#ifdef ASSERT
// make sure rdx was multiple of 8
Label L;
// Ignore partial flag stall after shrl() since it is debug VM
__ jccb(Assembler::carryClear, L);
__ stop("object size is not multiple of 2 - adjust this code");
__ bind(L);
// rdx must be > 0, no extra check needed here
#endif
// 按字节进行循环遍历对内存,初始化对象实例内存为零值
{ Label loop;
__ bind(loop);
__ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 1*oopSize), rcx);
NOT_LP64(__ movptr(Address(rax, rdx, Address::times_8, sizeof(oopDesc) - 2*oopSize), rcx));
__ decrement(rdx);
__ jcc(Assembler::notZero, loop);
}
// 对象头的初始化
__ bind(initialize_header);
if (UseBiasedLocking) {
__ pop(rcx); // get saved klass back in the register.
__ movptr(rbx, Address(rcx, Klass::prototype_header_offset()));
__ movptr(Address(rax, oopDesc::mark_offset_in_bytes ()), rbx);
} else {
__ movptr(Address(rax, oopDesc::mark_offset_in_bytes ()),
(intptr_t)markOopDesc::prototype()); // header
__ pop(rcx); // get saved klass back in the register.
}
#ifdef _LP64
__ xorl(rsi, rsi); // use zero reg to clear memory (shorter code)
__ store_klass_gap(rax, rsi); // zero klass gap for compressed oops
#endif
__ store_klass(rax, rcx); // klass
{
SkipIfEqual skip_if(_masm, &DTraceAllocProbes, 0);
// Trigger dtrace event for fastpath
__ push(atos);
__ call_VM_leaf(
CAST_FROM_FN_PTR(address, SharedRuntime::dtrace_object_alloc), rax);
__ pop(atos);
}
__ jmp(done);
}
// 类没有被加载解析,会跳到此处执行
__ bind(slow_case);
__ pop(rcx); // restore stack pointer to what it was when we came in.
__ bind(slow_case_no_pop);
Register rarg1 = LP64_ONLY(c_rarg1) NOT_LP64(rax);
Register rarg2 = LP64_ONLY(c_rarg2) NOT_LP64(rdx);
//获取常量池地址,存入rarg1寄存器。
__ get_constant_pool(rarg1);
//获取new 指令后操作数,即类在常量池中的索引,放入rarg2寄存器
__ get_unsigned_2_byte_index_at_bcp(rarg2, 1);
//进入entry_point,进行类的加载和对象分配,并将分配的对象地址返回,存入rax寄存器中
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), rarg1, rarg2);
__ verify_oop(rax);
// continue
__ bind(done);
}
hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
这里走慢分配,会先去加载类,类加载初始化完以后,在执行对象分配
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
Klass* k_oop = pool->klass_at(index, CHECK);
instanceKlassHandle klass (THREAD, k_oop);
// Make sure we are not instantiating an abstract klass
klass->check_valid_for_instantiation(true, CHECK);
//初始化类
klass->initialize(CHECK);
//分配对象
oop obj = klass->allocate_instance(CHECK);
//保存结果,模板中会通过get_vm_result取回结果
thread->set_vm_result(obj);
IRT_END
hotspot/src/share/vm/oops/instanceKlass.cpp
对象的大小在类加载时就已经确定
instanceOop InstanceKlass::allocate_instance(TRAPS) {
int size = size_helper(); // Query before forming handle.
instanceOop i;
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
return i;
}
hotspot/src/share/vm/gc/shared/collectedHeap.inline.hpp
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
return (oop)obj;
}
hotspot/src/share/vm/gc/shared/collectedHeap.inline.hpp
HeapWord* CollectedHeap::common_mem_allocate_init(KlassHandle klass, size_t size, TRAPS) {
HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
init_obj(obj, size);
return obj;
}
hotspot/src/share/vm/gc/shared/collectedHeap.inline.hpp
慢分配仍然尝试先从TLAB分配,如果TLAB开启了的话,如果没分配到再尝试从堆分配
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
//从TLAB分配
HeapWord* result = NULL;
if (UseTLAB) {
result = allocate_from_tlab(klass, THREAD, size);
if (result != NULL) {
return result;
}
}
//从堆分配
bool gc_overhead_limit_was_exceeded = false;
result = Universe::heap()->mem_allocate(size, &gc_overhead_limit_was_exceeded);
if (!gc_overhead_limit_was_exceeded) {
}
hotspot/src/share/vm/gc/shared/collectedHeap.inline.hpp
HeapWord* CollectedHeap::allocate_from_tlab(KlassHandle klass, Thread* thread, size_t size) {
assert(UseTLAB, "should use UseTLAB");
HeapWord* obj = thread->tlab().allocate(size);
if (obj != NULL) {
return obj;
}
//线程TLAB空间不够,走这里
return allocate_from_tlab_slow(klass, thread, size);
}
hotspot/src/share/vm/gc/shared/collectedHeap.cpp
eapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {
//重新计算TLAB大小
size_t new_tlab_size = thread->tlab().compute_size(size);
thread->tlab().clear_before_allocation();
//分配一块新的TLAB
HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
thread->tlab().fill(obj, obj + size, new_tlab_size);
return obj;
}
hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp
为了调试堆分配,使用 -XX:-UseTLAB禁用TLAB分配
HeapWord* GenCollectedHeap::mem_allocate(size_t size,
bool* gc_overhead_limit_was_exceeded) {
return gen_policy()->mem_allocate_work(size,
false /* is_tlab */,
gc_overhead_limit_was_exceeded);
}
hotspot/src/share/vm/gc/shared/collectorPolicy.cpp
堆分配的策略是:
- 先尝试在年轻代并行无锁分配
- 分配失败,在尝试有锁状态下分配
- 还失败,再扩展堆空间再试
- 最终没有分配到则触发GC
HeapWord* GenCollectorPolicy::mem_allocate_work(size_t size,
bool is_tlab,
bool* gc_overhead_limit_was_exceeded) {
GenCollectedHeap *gch = GenCollectedHeap::heap();
*gc_overhead_limit_was_exceeded = false;
HeapWord* result = NULL;
// Loop until the allocation is satisfied, or unsatisfied after GC.
for (uint try_count = 1, gclocker_stalled_count = 0; /* return or throw */; try_count += 1) {
//尝试在年轻代并行无锁分配
Generation *young = gch->young_gen();
if (young->should_allocate(size, is_tlab)) {
result = young->par_allocate(size, is_tlab);
if (result != NULL) {
return result;
}
}
uint gc_count_before; // Read inside the Heap_lock locked region.
{
MutexLocker ml(Heap_lock);
bool first_only = ! should_try_older_generation_allocation(size);
//尝试再次分配
result = gch->attempt_allocation(size, is_tlab, first_only);
if (GCLocker::is_active_and_needs_gc()) {
if (is_tlab) {
return NULL; // Caller will retry allocating individual object.
}
if (!gch->is_maximal_no_gc()) {
//尝试扩展堆大小再分配
result = expand_heap_and_allocate(size, is_tlab);
// Result could be null if we are out of space.
if (result != NULL) {
return result;
}
}
}
//触发GC
VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);
VMThread::execute(&op);
if (op.prologue_succeeded()) {
result = op.result();
if (op.gc_locked()) {
assert(result == NULL, "must be NULL if gc_locked() is true");
continue; // Retry and/or stall as necessary.
}
return result;
}
}
hotspot/src/share/vm/gc/serial/defNewGeneration.cpp
年轻代
HeapWord* DefNewGeneration::par_allocate(size_t word_size,
bool is_tlab) {
HeapWord* res = eden()->par_allocate(word_size); //最终调用par_allocate_impl
if (CMSEdenChunksRecordAlways && _old_gen != NULL) {
_old_gen->sample_eden_chunk();
}
return res;
}
hotspot/src/share/vm/gc/shared/space.cpp
inline HeapWord* ContiguousSpace::par_allocate_impl(size_t size) {
do {
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {
HeapWord* new_top = obj + size;
HeapWord* result = (HeapWord*)Atomic::cmpxchg_ptr(new_top, top_addr(), obj);
if (result == obj) {
return obj;
}
} else {
return NULL;
}
} while (true);
}
hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp
从年轻代还是老年代尝试,取决于should_allocate,先尝试年轻代
HeapWord* GenCollectedHeap::attempt_allocation(size_t size,
bool is_tlab,
bool first_only) {
HeapWord* res = NULL;
if (_young_gen->should_allocate(size, is_tlab)) {
res = _young_gen->allocate(size, is_tlab);
if (res != NULL || first_only) {
return res;
}
}
if (_old_gen->should_allocate(size, is_tlab)) {
res = _old_gen->allocate(size, is_tlab);
}
return res;
}
hotspot/src/share/vm/gc/serial/defNewGeneration.cpp
年轻代的Eden空间没分配到,则尝试从form空间分配
HeapWord* DefNewGeneration::allocate(size_t word_size, bool is_tlab) {
HeapWord* result = eden()->par_allocate(word_size);
if (result != NULL) {
if (CMSEdenChunksRecordAlways && _old_gen != NULL) {
_old_gen->sample_eden_chunk();
}
} else {
//从from空间分配
result = allocate_from_space(word_size);
}
return result;
}
TLAB是属于线程的缓存区,因此在分配时不需要加锁同步,使用指针碰撞算法,分配速度较快。因为分配算法的关系,释放时需整块释放,无法按对象释放。在向堆申请时首先会向年轻代申请,申请不到时尝试扩展堆再申请,还是申请不到时,便触发MinorGC。老年代不足时还会触发FullGC。