读书笔记——Java对象

it2024-11-14  3

前言 本文探讨的是new对象时的内存分配情况,主要是书本知识的总结和提炼,不过作者的另外一篇博客提到了对new对象时初始化的总结和理解,比较适合一起看。 链接:https://editor.csdn.net/md/?articleId=108236252 对象的创建 类加载检查通过后,虚拟机将会为新生对象分配内存,对象所需的内存在类加载完成后便可以完全确定。假设Java堆完全规整,使用过的内存和未使用的内存中间放着一个指针作为分界点的指示器,所分配的内存就仅仅时把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为指针碰撞。但如果内存不是规则的,虚拟机就必须维护一个列表记录那些内存块可用,在分配的时候从列表中找到一块足够大的空间分配给对象实例,并更新列表上的记录。这种分配方式称为空闲列表。选择那种方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理的能力决定。 指针碰撞在并发下的问题:对象正在给A分配内存,指针还未来得及修改,对象B又使用了原来的指针来分配内存的情况。两种解决方案:一是,通过CAS配上失败重试来保证操作的原子性,二是每个线程预先在Java堆中分配一小块内存,内存分配的动作按线程规划在不同的空间中进行。这一小块内存称为本地线程分配缓冲(TLAB)。 内存分配完毕后,虚拟机必须将分配的空间都初始化为零值,保证对象的实例字段在Java代码中可以不赋初值就可以直接使用。 对象的内存布局 上面完成了对象的内存分配,现在自然而然到了对象的内存布局。对象的内存布局可以划分为三个部分:对象头、实例数据、对齐填充。 对象头部分包括两类信息:第一类是用于储存对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为Mark Word, mark word被设计成一个有着动态定义的数据结构,以便在极小的空间内存储更多的数据,根据对象的状态复用自己的储存空间。 另外一部分是类型指针。即对象指向它的类型元数据的指针。来确定对象是哪个类的实例。如果对象是一个Java数组,那在对象头中还必须有一块记录数组长度的数据。 实例数据:对象真正存储的有效信息,即我们在程序代码中所定义的各个类型的字段内容。HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops,可以看到相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类定义的变量会出现在子类之前。 对齐填充:HotSpot虚拟机的自动内存管理系统要求对象的大小都必须是8字节的整数倍,对象头部分已经被精心设计成八字节的整数倍,如果对象实例数据部分没有对齐的话,就需要对齐填充来补全。 对象的访问定位 创建对象自然是为了使用对象,Java程序会通过栈上的reference数据来操作堆上的具体对象。《Java虚拟机规范》中规定了reference类型只是一个指向对象的引用。并没有定义这个引用应该通过什么样的方式去定位,所以对象访问方式也是由于虚拟机实现而定的。 如果使用句柄访问,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自具体的类型信息。 如果是指针直接访问,reference中访问的就直接是对象地址。 两种对象访问各有优势,句柄访问的好处,就是对象被移动时只会改变句柄中的地址,而不用改变reference。直接指针访问的好处就是速度更快,因为它节省了一次指针定位的时间开销。HotSpot虚拟机主要使用的是指针直接访问。

最新回复(0)