JVM(2)--深入理解java对象创建始终
java对象探秘
java是一门面向对象的语言,我们无时无刻不在创建对象和使用对象,那么java虚拟机是如何创建对象的?又是如何访问对象的?java对象中究竟存储了什么运行时所必需的数据?在学习了java虚拟机数据的存储区域后,再来探究一下对象是如何产生的?
对象是如何创建的?
对于使用者来说,创建对象仅仅只是new一个对象而已。但对于虚拟机来说,却是一系列的过程。
1.检查对象所属类是否进行过类加载
虚拟机在遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过。如果没有,那必须先执行相应的类加载过程。其实就是该类的实例对象是不是之前创建过。
2.为对象分配内存
对象所分配的内存在类加载完之后就能完全确认,但是如何在堆上为对象分配内存呢?怎么分配合理呢?就内存分配的算法,java虚拟机里面有两种策略,一种是“指针碰撞”,一种是“空闲列表”,采用哪种策略取决于内存是否规整,而内存是否规整又取决于收集器。这两种算法分别适应于两种收集器–带Compact过程的收集器,基于Mark-sweep的收集器。
- 指针碰撞:假设java堆中的内存是绝对规整的,所有用过的内存放在一边,所有空闲的内存放在另一边。中间放着一个指针用作分界区的指示器。那么分配内存就仅仅是把那个指针先空闲区域移动一个与对象同样大小的距离
- 空闲列表:如果java堆中的内存并不是规整的,用过的内存与未使用的内存交错在一起,那么就无法使用指针移动的方法来分配内存了。虚拟机会维护一个空闲列表,记录那些内存块是可用的,那些是不可用的,在分配内存的时候从空闲列表找到一个足够大的内存区域用来存放对象实例,并更新空闲列表。
-
并发情况下内存分配策略:在多线程环境下,为了保证对象成功创建,一般会采用CAS配上失败重试的方法保证更行操作的原子性,也还用另外一种方法,就是把内存分配的操作按照线程划分在不同的空间中进行(使用本地线程分配缓冲,TLAB)。
内存分配完之后,虚拟机需要将分配到的内存空间都初始化为零值(除对象头)
3.设置对象头
设置对象头信息,例如对象哈希码,对象GC分代年龄,类的元数据等。在Java虚拟机上创建对象的过程就算完成了,但是从Java程序上看,还没有进行<init>初始化。
对象的内部构造是这样的?
如何访问对象?
- 使用句柄
描述:reference指针指向一个句柄池,句柄池内包含了示例数据对象指针,对象类数据指针。
优点:reference存储的是稳定的句柄地址,在对象被移动(内存回收时)只改变句柄中的实例数据对象,而reference不需要修改 - 使用直接指针
描述:reference指针直接指向对象实例数据,对象实例数据内包含对象类数据指针
优点:速度更快,节省一次指针定位的时间
缺点:java对象访问频繁,开销较大
现在的Sun HotSpot一般采用句柄访问对象的方法。