对象内存结构(hotspot)
Posted lollong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对象内存结构(hotspot)相关的知识,希望对你有一定的参考价值。
一.对象的整体结构
1.对象头
图中可以看出对象头分为MarkWord与Class对象指针,其中MarkWord标识了对象运行时的各种属性与状态值,哈希码(HashCode).GC分代 年状 态标志、线程持有的锁、偏向线程ID、偏向时间戳等. 而Class对象指针则指向一个类在被类加载器读入内存后生成 的Class对象的内存地址,这样就可以通过对象判断它是哪个类的实例 。。。class对象指针:这段空间是用来保存自己的父类通过类加载器加载到内存后生成的类的Class对象的地址,这样就可以通过对象知道它是属于哪个类的。
2.实例数据
实例数据是保存对象真正有效的数据,也就是对象的各种字段信息,其中也包括从父类中继承的字段,都保存在这里,当然方法不在这里,在类中。而且它的内存具体分配结构是受jvm分配策略与字段在源码中的顺序来决定的,默认是相同宽度的字段分配在一起,在这个前提下父类字段在子类字段前面,而且子类中较窄的字段也可能被分配到父类中的间隙中。
3.对齐填充
因为hotspot虚拟机的内存管理系统要求内存的起始地址必须是8的整数倍,所以这段填充就是为了保证地址是8的整数倍。
二.对象的内存分配
就目前我所知道的内存分配方式有两种,一种是指针碰撞,也就是假设Java的内存是完全规整的,分配了的内存在一边,未分配的内存在另一半,每次分配完内存后,指针就往后移动。另一种就是空闲列表,也就是每分配一块内存都会在一个列表里进行记录,这样通过这个列表就可以知道那些内存是可以使用的,那些是已经分配了的。 所以具体使用哪种分配方式是由内存是否规整来决定的,而内存是否规整又是由垃圾回收器的具体算法来决定的,比如ParNew,Serial等采用指针碰撞,而CMS就是采用的空闲列表。
还有一个比较重要的问题就是,在多线程的情况下,创建对象分配内存是存在线程安全问题的,有可能这个线程还没把一个对象完全创建完,就切换到另一个线程执行了并且使用了这个还没完全创建完的对象,那就由问题了,所以对象创建必须是原子的,不可分的,那么虚拟机是采用的CAS加上失败重试来解决的,另一种办法就是很干脆的,直接给每个线程分配一些用来创建对象的内存空间,这样每个线程中的对象就是独立的了,不会被其他线程访问到了,也就不存在线程安全了,每个线程都有程序计数器来保存当前代码执行的位置,等到切换回来就根据程序计数器继续执行就可以了。
三.对象的访问机制
1.句柄访问方式
在栈中保存的reference是一个句柄,通过这个句柄可以访问句柄池中的一块信息,这块信息保存了这个对象的内存地址信息与它的类的地址信息
2.直接地址访问方式
栈中的reference保存的就是这个对象的内存地址,通过这个地址就可以访问到内存中的对象,在对象中在保存对象类型的信息
3.第一种方式的好处就是当内存的地址改变后,不需要改变栈中的refenence的句柄值,只要改变堆中句柄池的地址值就可以了,坏处就是访问速度没有第二种方式快,不过如果对象的内存地址频繁改变,那么就也需要频繁的改变栈中的refrence中的地址值,就hotspot来说,它是采用的第二种方式。
四.实例
那么一个int类型的对象占躲到的空间,一个int是4个字节,而int会自动装箱为Integer对象,而对象由标识位,MarkWord,class指针,实例数据,对齐填充,MarkWord 4个字节,class指针4个字节,实例数据4个字节,这就是12个字节,而对象大小要是8的整数倍所以填充4个字节,总共16个字节
不过需要注意的是数组与普通对象不同,它还需要一个字段来保存自己长度值,所以如果是数组就还要加上一个int类型的length字段,也就是再多加四个字节。
以上是关于对象内存结构(hotspot)的主要内容,如果未能解决你的问题,请参考以下文章