对象的创建
通过new 类名创建:
- 根据new的参数,在常量池中定位一个类的符号引用
- 如果没有找到这个符号引用,说明类还没有被加载,则进行类的加载、解析和初始化
- 虚拟机为对象分配内存(位于堆中)
- 初始化对象,将分配的内存初始化为零值(不包括对象头)
- 调用对象的
<init>
方法- 只有执行了
init
方法后,对象才是按照程序员的意愿初始化完成了 - 包括代码块,构造方法等
- 只有执行了
给对象分配内存
指针碰撞:把指针向空闲内存方向移动与对象占用内存大小相等的距离
空闲列表:记录堆中还有哪些空闲内存,等需要给对象分配内存时,就从列表中寻找
线程安全性问题
当给一个对象分配内存后,还没来得及更新列表或者指针,另外的线程又给另外的对象分配了这一块内存,这就出现了线程安全性问题。
- 加锁实现线程同步
- 本地线程分配缓冲(TLAB):每个线程划分一块单独的区域,互不影响
对象结构
- Header(对象头)
- 自身运行时数据(Mark Word)
- 哈希值(由对象的hashcode()得来),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳
- 类型指针:对象指向它本类的元数据的指针,虚拟机根据这个指针确定对象是哪个类的实例。不是一定需要。
- 也就是说,查找对象的元数据信息并不一定要经过对象本身。
- 自身运行时数据(Mark Word)
- instanceData:对象的有效信息,如从父类继承的等,会受到虚拟机分配策略的影响。
- Padding:填充,没有实际意义
对象的访问
建立对象是为了使用对象,Java程序需要通过栈上的引用数据来操作堆上的具体对象,而引用类型在虚拟机规范中只是规定了一个指向对象的引用,没有定义具体方式去定位、访问堆中的对象具体位置。
目前,引用类型访问堆中对象主要有两种方式:
- 句柄访问
- 在java堆中划分一块内存作为句柄池
- reference中存储的是对象的句柄地址
- 句柄中包含对象实例数据和类型数据各自的具体地址
- 特点:
- 节省栈内存
- 因为栈内存中的引用始终是句柄池就可以了,不会变化,对象被修改时,只需要改变句柄池中的指针,不需要改变reference数据。
- 直接访问
- reference中存储的直接就是对象地址
- Java堆对象的布局中必须考虑如何放置访问类型数据的相关信息
- 特点:速度快,节省了一次指针定位的时间开销。
参考
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2020/03/24/jvm/2020-03-24-jvmObjectDistribution/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!