【Java】Java对象详解

 

名词解释:

符号引用:符号引用使用一组符号来描述所引用的目标,可以是任何字面量,只要能够无歧义地定位到该引用目标就行了。由于Java源代码编译成字节码的时候,虚拟机不知道所引用的目标的实际地址,所以需要用一个符号来代替引用的对象。比如说Student类引用了People类,但Student类不知道People类的实际地址,因此用”People“这个字面量来表示这个引用的类,当然实际中并不是用“People”这个字面量来表示的,而是根据虚拟机规范来表示类信息。

直接引用

1、直接引用可以是一个指向引用目标地址的指针

2、地址相对偏移量

3、一个能够定位到该引用目标的句柄

在JVM类加载过程中的解析阶段,虚拟机就会把字节码中的符号引用替换为直接引用。

 

一、对象的创建

对象的创建在Java语言层面就是使用new关键字就可以创建一个对象,但是在虚拟机层面,创建一个对象主要做了以下几步工作:

1、检查new指令的参数是否能在常量池中定义到一个类的符号引用

2、检查这个符号引用所代表的类是否已经被虚拟机加载、解析和初始化,若没有,则必须先执行相应的类加载过程

3、加载过程结束后,新生对象所需要的内存已经确定,虚拟机为该新生对象从堆中分配内存存放该对象

4、分配完内存后,虚拟机将该对象内存空间初始化为0(不包括对象头),这也是为什么Java中每一种类型数据都有对应的零值

5、虚拟机设置该对象的对象头,填入对象头数据,比如该对象是哪个类的实例、如何找到类的元数据、该对象的哈希码等

6、从虚拟机角度来说,对象创建已经完成,但是对于语言层面来说,对象创建刚开始,对象创建之后,会调用构造方法,依照程序员的想法对该对象进行初始化

 

新对象内存分配方式:

1、指针碰撞(Bump the Pointer):该方式假设对内存是绝对规整的,即已分配的内存在一边,另一边则是未分配的内存,并记录着一个指向这两块区域中间的指针,分配内存给新对象时,只需要将该指针向未分配内存方向移动该对象大小的内存区域就行了

2、空闲列表(Free List):该方式维护的内存可以是不规整的,虚拟机维护一张位图表,用以记录虚拟机内存的使用情况,分配时从该表中找到一块足够大的内存划分给该对象,并更新表记录

 

划分内存时的确保并发线程安全性:

1、采用CAS配上失败重填的方式保证更新操作的原子性

2、采用TLAB方式,如果一个线程要分配内存,就在该线程的TLAB上分配,当TLAB需要扩展时再进行同步操作扩展TLAB,由于TLAB是线程私有的,所以是线程安全的

 

二、对象的内存布局

对象在内存中大概可分为3个部分:对象头、实例数据和对齐填充

1、对象头:

(1)存储自身的运行时数据如hashcode、GC分代年龄、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据在32位和64位机中分别占32bit和64bit,称为“Mark Word”,对于对象的不同状态,该数据会动态变化

(2)类型指针,即对象指向它的元数据的指针

2、实例数据:即该对象真正存储的有效数据

3、对齐填充:仅仅起到占位符作用,对于规定了“数据块”的虚拟机来说,如果一个数据块中没有使用全部空间,则需要用占位符来填充,以保证块操作

 

三、对象的访问定位

对象的访问定位与虚拟机栈有关,虚拟机栈中存放了局部变量表,其中就有“对象引用”,这就是用来访问定位一个对象的根据,该对象引用一般有两种实现方式:句柄和直接指针。

1、句柄访问

使用这个方式的话,虚拟机会在堆中划分一块区域来作为句柄池,reference数据中存储的就是该对象的在句柄池中的句柄地址,该句柄又包含了对象实例数据与类型数据各自的具体信息

2、直接访问

使用直接指针访问的话,reference中存储的就是该对象的直接地址了

两种方式的比较:

句柄最大的好处就是reference中只存放着稳定的句柄地址,在对象地址移动时只需要改变句柄中的实例指针就行了,不需要修改reference本身

而直接指针访问最大的好处就是速度会更快,因为只需要访问一次就可获取到实例的数据地址,而句柄需要访问两次,由于对象访问非常频繁,因此这个访问开销会积少成多 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章