jvm之对象

记录一下学习jvm的过程,基本就是《深入理解Java虚拟机》这本书的学习笔记。

对象的创建:在我们使用java过程中,创建对象就是简单的使用new关键字就可以了,那么在虚拟机中创建一个对象,我们可以想象到首先肯定是找到这个类,然后是在内存中分配存储空间,然后是做初始化工作。

首先就是检查new的这个参数是否可以在常量池中定位到一个类的符号引用,并检查这个类是否已经被加载、解析和初始化过。如果没有,则执行相应类的加载过程。

然后是分配空间,前面说过一个对象的大小在编译阶段就已经完全确定,所以直接在堆中划分出一块确定大小的区域。划分区域会有两种情况,一种是内存区域很规整,已被使用的空间都是连续的,空闲区域和已被使用的区域中间有一个指针来标识,所以分配区域只需要把指针像空闲区域移动相应大小的空间既可以,这种方法叫做“指针碰撞”;还有一种情况是内存区域不规整,已使用的区域分散,那么就需要一个“空闲列表”来表名所有空闲的区域,在一块足够大的空闲区域上分配空间。不同的分配方式取决于垃圾回收的方式,如果垃圾回收带压缩整理,则回收后内存区域是规整的。这里还有一个问题,多线程的情况下,分配空间也是会有并发问题的,解决并发问题有两种方案,一种是分配空间进行同步处理,虚拟机采用的CAS配上失败重试方式保证的,在轻量级锁的获取,改写锁对象的mark word也是采用的cas方式,jdk中的很多同步类也都是采用这种方式。另一种就是把内存分配的动作按照线程划分到不同的空间中,即每个线程在Java堆中预先分配一小块内存区域,成为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),线程分配内存时,先在TLAB上分配,只有TLAB用完并分配新的TLAB时才会同步锁定。

接着是虚拟机要将分配的空间进行初始化,不包括对象头,使对象实例字段在Java代码中可以不用复制就可以使用。

最后就是分配对象头,包括对象是哪个类的实例,类的元数据信息、哈希码、GC分代年龄等等。

在虚拟机角度,对象就创建完了,从我们使用代码角度,还需要进行构造函数初始化,所以在new指令后执行<init>方法,来初始化,这才算是真正产生了一个对象。

对象的内存布局:在HotSpot虚拟机中,对象可以分为三个区域:对象头,实例数据,对齐填充。

对象头包含两部分内容,第一部分用来存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等等,在32位和64位虚拟机中分别为32位和64位,称为“mark word”。考虑到存储空间效率,虚拟机把这部分内容设置为非固定的数据结构,会根据对象状态服用自己的空间。另一部分是类型指针,对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。这个所有的虚拟机都在对象上保存类型指针,因为对象的定位主流分为两种方式,一种是句柄方式,java堆中将会划分一块内存作为句柄池,栈上的对象引用指向的就是句柄池的地址,句柄包括了对象实例数据与类型数据各自的指针,这种方式就不要对象保存类型指针。另外一种方式是直接指针访问,栈上对象的引用指向的就是对象地址,此时对象就需要保存类型指针。这两种方式各有好处,句柄就是栈中保存的是稳定地址,对象移动时只需要改变句柄中的实例数据指针。直接指针访问是速度快,在HotSpot中使用的第二种。如果对象是数组,对象头还需要保存数据的长度。

实例数据保存的就是对象的具体数据,无论是从父类继承的还是子类定义的,都要记录。存储顺序会受到虚拟机分配策略参数和字段在java源码中定义的顺序影响,HotSpot默认顺序是longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Pointes),相同长度的字段都会分配到一起,父类定义的会出现子类前。

对齐不一定存在,因为HotSpot要求对象的起始地址必须是8的倍数,而对象头正好是8的倍数,如果实例数据不是8的倍数,则补齐。


接下来是代码实验,验证各个区域的存储内容。

文字内容结束,敲代码去喽......

发布了45 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/ly262173911/article/details/78429278