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基于后者。
- Note of deep JVM(2)_hotspot JVM
- Note of deep JVM(1)
- Note Of Deep JVM(3)_垃圾收集算法
- JVM(2)-JVM原理
- deeplearning Note : Practical aspects of Deep Learning
- JVM
- jvm
- JVM
- JVM
- JVM
- JVM
- jvm
- jvm
- jvm
- JVM
- JVM
- jvm
- JVM
- 大平台 or 小应用
- 计算1到n之间的所有数的平方和立方
- php实现协程,真正的异步
- 第十一周训练总结(二)
- Android_GPS详解
- Note of deep JVM(2)_hotspot JVM
- 错排。中的阶乘要注意不能超过20,不然longlong也救不了
- 设计模式-工厂模式
- 6.14
- 映客小视频怎么导出?映客小视频下载到本地的方法
- ButterKnife在RecyclerView adapter中的使用
- myeclipse基本的快捷键
- Github博客搭建
- JAVA反射机制