jvm读书笔记-2.3内存对象的创建、布局和访问

来源:互联网 发布:linux创建新用户命令 编辑:程序博客网 时间:2024/05/21 05:57

一背景介绍

本文属于深入理解java虚拟机读书笔记系列(周志明)。第二章java内存区域与内存溢出异常。主要分为三节介绍:运行时数据区域,参见之前的整理(运行时数据区)Hotspot虚拟机对象(本文),还有OutofmemoryError异常(参见java内存溢出分析)。下面是详细内容。

二对象的创建

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,参见(类加载及器初始化)。

若类加载检查通过,则将为新生对象分配内存,对象所需内存的大小在类加载完后就可确定,有两种方式,第一种是“指针碰撞”,指针碰撞方式指在分配内存时,就像一边是用过来的,一块是没用过的,中间有一个指针是分界线,在需要新分配一块内存时,指针就像空闲内存这块移动与所需内存相同大小的距离,当然需要java内存是规整的。第二种方式是”空闲列表”,就是如果java内存不是规整的,就是空闲内存和已用内存是交错的,那么肯定不能使用“指针碰撞“,此时需要一个列表来记录哪些是空闲内存块,在分配内存的时候就找到相同大小的块去分配,然后在列表中更新就OK了。这两种方式的采用取决于java内存是否规整,java堆是否规整又是取决与垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial,ParNew带Compact(复制)算法的过程的收集器的时候,如CMS垃圾收集器采用的是标记-清理算法就采用”空闲列表“。

解决多线程下线程安全问题:一种是对分配内存空间的动作进行同步处理,Java虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种方式就是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB,Thread Local Allocation Buffer)。 虚拟机是否使用TLAB,可以通过参数-XX: +/-UseTLAB来设定。

内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(不含对象头)。

对对象进行必要的设置。(对象是哪个类的实例,如果能找到类的元数据信息,对象的哈希码,对象的GC分代年龄)

一般来说,执行new指令之后会执行对象的init方法。

后记:这里说的是对象的创建,从完成流程来看,对象在完成生命周期销毁后,就是牵扯内存回收部分。

三对象的内存布局

之前再java并发那里也提到过对象头,这里补充下相关知识。

在Hotspot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。

3.1对象头

对象头用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。


Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下:
25 bit4bit1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode对象分代年龄001

在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:

锁状态

25 bit

4bit

1bit2bit23bit2bit是否是偏向锁锁标志位轻量级锁指向栈中锁记录的指针00重量级锁指向互斥量(重量级锁)的指针10GC标记空11偏向锁线程IDEpoch对象分代年龄101

对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。另外,如果对象是一个Java数组,对象头信息还必须有一块用于记录数组长度的的数据。

3.2实例数据

存储的是对象真正的有效信息,包括程序代码中所定义的各种类型的字段,包括从父类继承下来的,和在子类中定义的(才可以使用多态),存储顺序受虚拟机分配策略参数和字段在Java源码定义的顺序影响,Hotspot虚拟机默认的分配策略为longs/doubles,ints,shorts/chars,bytes/booleans,oop(Ordinary Object Pointers),可以看出,相同字段的宽度总是被分配到一起,这也是我们在定义变量时要尽量采用最小变量的原因。父类的变量会出现在子类之前。弱CompactFields为true,那么子类中较窄的变量也可能会插入到父类变量的空隙之中。 

3.3 对象填充

对象填充并不是必须存在的,仅仅起着占位符的作用,由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,而对象头部分正好是8字节的整数倍(1or2),因此,当对象实例数据部分没有对齐时,就需要对齐补充来补全。 

主要是使用代理方式使用 Instrumentation,进行测试对象的size。具体参照<java常见对象占用内存大小>

四对象的访问定位

建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。

句柄的方式会在java堆中划分一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包括了对象实例数据与类型数据各自的具体地址信息。 
直接指针就是reference直接指向堆中具体的对象 
句柄最大的好处是reference存储的是稳定的句柄地址,对象被移动只会改变句柄中的实例数据指针,直接对象访问就是速度快,节省了一次指针定位的时间开销。Hotspot是使用直接指针访问。


0 0
原创粉丝点击