Note of deep JVM(2)_hotspot JVM

来源:互联网 发布:端口2482 编辑:程序博客网 时间:2024/06/14 05:52

对象创建


当JVM遇到一条new指令,首先会检查这个指令参数能否在常量池中定位到一个类的符号引用,并检查这个引用代表的类是否已被加载,解析,初始化过。如果没有,则先执行上述过程。然后虚拟机将为新生对象分配内存,对象所需的内存空间在加载完成后即可完全确定。一般来说,分配的方式有两种

1.指针碰撞
java堆中内存绝对规整,从指针向空闲内存移动与对象大小相等的距离

2.空闲列表
虚拟机维护一个列表,记录哪块内存是可用的,在分配时从列表中找到一块足够大的空间,并分配给对象实例,并更新列表

选择哪种分配方式取决于java堆内存是否规整。

分配内存的原子性

解决方案1:cas操作
解决方案2:每个线程单独在java堆中分配一小块内存,称为本地线程分配缓冲区,只有在缓冲区满时,才需要同步锁定,虚拟机是否使用TLAB,可以用-Xx:+/-UseTLAB参数来设定

在内存分配完后,虚拟机需要将分配到内存空间的都初始化为0(不包括对象头)。

接下来,虚拟机将会为对象实例设置对象头,对象头主要包含:这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的hashcode,对象的GC分代年龄等信息。

最后执行Init方法。

对应源码:

CASE(_new): {        u2 index = Bytes::get_Java_u2(pc+1);        constantPoolOop constants = istate->method()->constants();        if (!constants->tag_at(index).is_unresolved_klass()) {          // Make sure klass is initialized and doesn't have a finalizer          oop entry = constants->slot_at(index).get_oop();          assert(entry->is_klass(), "Should be resolved klass");          klassOop k_entry = (klassOop) entry;          assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");          instanceKlass* ik = (instanceKlass*) k_entry->klass_part();          if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {            size_t obj_size = ik->size_helper();            oop result = NULL;            // If the TLAB isn't pre-zeroed then we'll have to do it            bool need_zero = !ZeroTLAB;            if (UseTLAB) {              result = (oop) THREAD->tlab().allocate(obj_size);            }            if (result == NULL) {              need_zero = true;              // Try allocate in shared eden        retry:              HeapWord* compare_to = *Universe::heap()->top_addr();              HeapWord* new_top = compare_to + obj_size;              if (new_top <= *Universe::heap()->end_addr()) {                if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {                  goto retry;                }                result = (oop) compare_to;              }            }            if (result != NULL) {              // Initialize object (if nonzero size and need) and then the header              //根据是否启用偏向锁来设置对象头信息              if (need_zero ) {                HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;                obj_size -= sizeof(oopDesc) / oopSize;                if (obj_size > 0 ) {                  memset(to_zero, 0, obj_size * HeapWordSize);                }              }              if (UseBiasedLocking) {                result->set_mark(ik->prototype_header());              } else {                result->set_mark(markOopDesc::prototype());              }              result->set_klass_gap(0);              result->set_klass(k_entry);              //对象引用入栈              SET_STACK_OBJECT(result, 0);              UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);            }          }        }        // Slow case allocation        CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),                handle_exception);        SET_STACK_OBJECT(THREAD->vm_result(), 0);        THREAD->set_vm_result(NULL);        UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);      }

对象内存布局


对象在内存中存储的布局分3块:对象头,实例数据,对齐填充

1.对象头
除了上面所说的外,还包含锁状态标志,线程持有锁,偏向线程ID,偏向时间戳;除此之外,对象头还包含另一部分:类型指针。即对象指向它的类元数据的指针,虚拟机可通过这个指针来确定对象是哪个类的实例。

2.实例数据
实例数据存储顺序收到虚拟机分配的策略参数和字段在java源码中定义的顺序影响。hotspot默认分配策略为longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Pointers),可以看出,相同宽度的字段总是被分配到一起。
在满足以上前提条件下,弗雷中定义的变量会出现在子类之前,如果compactFields参数设置为True(默认),那么子类中较窄的变量也会可能被插入到弗雷的变量空隙之中。

3.对齐填充
不是必须,填充站位作用,由于hotspot自动管理内存系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍,对象头正好是8字节的倍数,因此对象实例数据没有对齐时,就需要对齐填充来补全。

对象访问定位


reference类型在java虚拟机中只规定了一个指向对象的引用,并没有定义怎么取定位,怎么定位取决于不同虚拟机实现,主流的都是通过句柄和直接指针两种。

1.如果是使用句柄的话,java堆中将会划分出一款内存来作为句柄池,reference就是对象的句柄地址,句柄中则包含对象实例数据与类型数据各自的具体地址信息。如图:
这里写图片描述

2.直接指针,那么java堆对象的布局就必须要考虑如何房子访问类型数据的相关信息,reference存储的直接就是对象地址:如图:这里写图片描述

前者在对象被移动(GC期间移动非常频繁)时,只会改变句柄中的实例数据指针,而reference不需要被修改
后者速度更快,节省了一次指针定位的时间开销,由于对象访问非常频繁,因此这类开销是一项非常可观的执行成本。hotspot基于后者。

原创粉丝点击