深入探讨HotSpot虚拟机在JAVA堆中对象分配,布局和访问的过程.
对象创建过程
这里说的创建对象是不包括数组和Class对象的.
1.首先检查这个类是否已被加载,解析和初始化过.如果没有,那必须先执行相应的类加载过程.
- 接下来为新生对象分配内存.对相关所需内存的大小在类加载完成后便可完全确定.
- 内存分配完成后,虚拟机需要将分配到的内存空间全都初始化为零值.(不包括对象头),如果使用TLAB这一工作也可以提前至TLAB分配时进行.
- 接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息放在对象头中,
- 执行init方法.按照程序员的意愿进行初始化.
对象的内存布局
对象在内存中存储的布局可以分为三块区域:对象头(header) ;实例数据(Instance Data)和对齐填充(Padding).
- 对象头共有两部分内容,一部分是MarkWord,根据标志位的不同被用来存储不同的内容,包括:哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,对象头的另外一部分是类型指针,只想他的类元数据的指针.但是并不是所有的虚拟机实现都必须在对象数据上保留类型指针.另外如果对象是数组,那再对象头中还必须有一块用于记录数组长度的数据.
- 实例数据部分是对象真正存储的有效信息,无论是父类继承下来的,还是子类中定义的都需要记录下来.存储顺序会受到虚拟机分配策略参数和字段在JAVA源代码中定义顺序的影响.
- 对齐填充部分并不是必然存在的,仅仅是为了占位作用,因为HotSpot的自动内存管理系统要求对象起始地址必须是8字节的整数倍,就是说对象的大小必须是8字节的整数倍.因此这部分内容就是为了凑整.
对象的访问定位
对象的访问方式会因虚拟机的实现而不同,目前主流的有使用句柄和直接指针两种.
- 如果使用句柄访问的话,那么JAVA堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址.而句柄中包含了对象实例数据与数据各自的地址信息.
句柄两部分分别指向堆内存中的对象数据和方法区中的类型数据
- 如果使用直接指针访问,那么reference会直接指向堆中的对象实例,而对象中包含了一个指向方法区类型数据的指针.
两种方式各有优势,HotSpot使用的是指针方式,因为节省了一次指针定位的时间,更快.但是使用句柄,当对象被移动时(垃圾回收)只会修改句柄中的实例指针,而reference本身不需要修改.
虚拟机参数
- -Xms 堆内存最小值
- -Xmx 堆内存最大值
- -Xoss 本地方法栈大小 但是实际上在HotSpot当中是不区分本地方法栈和虚拟机方法栈的 所以不会有效果的.
- -Xss 栈容量设置
- -XX:MaxPermSize 最大方法区值
- -XX:PermSize 方法区大小
网友评论