JVM内存管理(一)--对象的内存管理
1.java堆中对象的创建,布局,访问
Java对象的内存表示模型是Oop-Klass模型。
1.1 对象的创建
①虚拟机在碰到一条new指令时,会检查这个指令参数(是一个符号引用,对应一个类)是否能在运行时常量池中定位到一个类,并且检查这个类是否被加载,解析和初始化.
②在类加载检查通过后,虚拟机需要为对象分配内存.对象所需内存的大小,在类加载完成后完全确定.这里涉及到对象内存分配的方法:指针碰撞和空闲列表.
指针碰撞:如果堆内存空间是绝对规整的,在已分配堆内存和未分配堆内存之间有一个指针作为分界点的指示器.在为新对象分配堆内存时,只需要将指针移动新对象大小的位置即可.
空闲列表:如果堆内存时不规整的,已分配的堆内存和未分配的堆内存犬牙交错,那么就必须维护一个内存列表,列表中记录了哪些堆内存已使用,哪些堆内存未使用,在为新对象分配堆内存时,选择一块较大的未使用的堆内存区域分配给对象,并更新列表记录.
注意:
在对象创建过程中,无论采用哪种方式,都有一个线程安全问题(如采用指针碰撞,线程A,B同时取到指针,分配对象会出现同时争用一块堆内存的情况).
如何解决这个问题呢?
一是在内存分配时候进行同步处理,使用CAS(Compare-And-Swap,比较并交换)保证原子性+失败重试的方式进行分配.
二是使用TLAB(Thread Local Allocation Buffer,线程本地分配缓存),就是每个线程在创建的时候,预先在Java堆中分配一小块内存区域,作为自己的TLAB,当线程需要分配对象堆内存时候,就在当前线程的TLAB上分配即可.
在虚拟机中,可以使用-XX:+/-UseTLAB虚拟机参数开启或者关闭使用TLAB.
③对象实例属性初始化为默认值,如int类型默认为0,引用的对象默认为null
④设置对象头(Object Header,对象头包含两部分:Mark Word,klass pointer)
⑤调用构造函数进行初始化,执行<init>方法.
1.2 对象在堆内存中的布局
1.2.1一个对象在堆内存中的布局包含两个部分:对象头(Header),实例数据(Instance Data).zzm书上把对齐补充也归为一部分,我觉得不妥.因为对齐补充是一种方式,而不属于内容.
①对象头(Header):对象头包含两部分内容,一部分内容是Mark Word,一部分是类型指针.
Mark Word:用于存储对象自身的运行时数据,包括对象的哈希码值,GC分代年龄,锁状态标志,偏向线程id,偏向时间戳等.见图片附件.
元数据指针(也叫类型指针,Klass Pointer):指向对象所属类元数据的指针,虚拟机通过这个指针确定对象是哪个类的实例.(不是全部虚拟机都是这么实现的,访问对象所属类信息,并不需要对象本身,见对象访问定位,可以通过栈帧的局部变量表)
注意:
如果对象是数组,对象头中还要增加一块记录数组长度的数据.
②实例数据:实例对象中包含的属性字段变量内容,包含从父类继承的.属性字段根据分配策略,相同大小的字段分配在一起,例如long/double都是64位,分配在一起.
③对齐补充:对齐补充只是一种字节补齐方式.HotSpot虚拟机的自动内存管理系统要求对象的大小必须是8字节的整数倍,不够整数倍的需要使用占位符对齐补充.
1.2.2 对象所占字节大小计算
①在32位虚拟机中,MarkWord占32bit,为4个字节;在未开启指针压缩的情况下,类型指针(Klass Pointer)占32位,为4个字节.对象头共为8个字节.
在开启指针压缩的情况下,只影响类型指针所占字节大小,为2个字节,这时,对象头是6个字节.
②在64位虚拟机中,MarkWord占64bit,为8个字节;在未开启指针压缩的情况下,类型指针(Klass Pointer)占64位,为8个字节.对象头共为16个字节.
在开启指针压缩的情况下,只影响类型指针所占字节大小,为4个字节,这时,对象头是12个字节.
③属性字段数据大小
基本数据类型:boolean、byte占用1个字节,char、short占用2个字节,int、float占用4个字节,long、double占用8个字节.
引用类型:每个引用类型占用4个字节
数组大小:特殊的地方,数组长度占用4个字节保存,其余同普通对象.
1.3 对象的访问定位
当程序要访问一个对象时,是通过栈帧的局部变量表中对象的引用类型(reference)操作的.在虚拟机规范中,只规定了reference类型需要指向具体的对象,但没有规定如何定位.
目前主流的访问方式有两种:句柄和直接指针.
句柄:如果使用句柄访问对象,那么在堆内存中需要划分出一块区域作为句柄池,reference类型指向的是句柄池中的句柄(引用类型中存放的是句柄的地址).在句柄中包含两部分,
一部分是指向对象实例数据的指针(对象实例数据在堆内存中),一部分是指向对象所属类的指针(对像所属类的指针指向的是方法区中的类信息).
直接指针:顾名思义,直接指针就是在栈帧的局部变量表中的reference类型直接指向了对象(引用类型中存放的是对象的地址).存储对象的堆内存包含对象实例数据和到对象所属类的指针(指向方法区的类信息).
对比:
句柄:使用句柄的优点就是稳定,当对象移动的时候,不需要改变引用类型指向的句柄地址.
直接指针:使用直接指针的优点就是速度快,省去了一次定位成本,目前HotSpot采用此方式.