当前位置: 代码迷 >> 综合 >> JVM 之 内存篇
  详细解决方案

JVM 之 内存篇

热度:38   发布时间:2024-02-04 18:11:33.0

介绍完java虚拟机运行时数据区之后,本篇将介绍这些数据是如何创建,如何布局以及如何访问的。

1:对象的创建

2:对象的内存布局

3:对象的访问定位

从语言层面上,创建对象通常仅仅是一个new关键字而已。而在虚拟机中对象是是怎么创建的尼,这次讲解的是普通java 对象,不包括数组和Class对象。当虚拟机接收到一个new指令的时候,

对象的创建:

1:首先检查这个指令的参数是否能在常量池中定位一个类的符号引用。

2:并且检查这个类的符号引用是否被加载,解析和初始化,如果没有的话,必须先执行相应类的加载过程。

3:在类加载过程检查通过,接下来就是为新生的对象,分配内存空间了,内存空间分配的方式和java堆采用的垃圾回收器是否带有压缩整理功能决定。没有的话,采用指针碰撞的方式(一边是空闲,一边是使用),有的话,采用空闲列表方式。在分配内存空间的时候,其实也是有安全隐患的,因为对象创建是频繁的在高并发的情况,假设是通过指针碰撞的方式分配内存空间,只是修改指针的地址,也有可能a分配了,没来及修改,b又同样的分配。为了解决这个问题,虚拟机采用CAS算法配上失败重试的方式保证更新操作的原子性,另一种是根据线程,来划分,每个线程在java堆中,预先分配一小块内存,这被称为,本地线程分配缓冲Thread local Allocation Buffer 。等用完的时候才会进行分配新的TLAB ,这时候才需要进行同步锁定,虚拟机是使用TLAB,可以用-XX:+/-UseTLAB参数来设定。

4:内存分配完了,虚拟机需要将分配的内存空间,都初始化为零值,不包括对象头。如果使用TLAB,这一工作提前至,分配TLAB之前。这保证了对象的实例字段,可以在不初始化值,就可以使用,程序访问到的都是对应数据类型,对应的零值。

5:接下来就要,对对象进行必要的设置了。例如,这个对象是哪个类的实例,如何才能找到这个类的元数据,对象的哈希吗,对象的GC分代年龄等信息。这些信息存放在对象的对象头里。还会根据当前虚拟机运行的状态,如是否启用偏向锁等,对象头会有不同的设置方式。

6:从虚拟机的角度,来看,一个新的对象以及创建好了,但是从java程序的角度来看,创想创建才刚刚开始,还没有初始化尼,init方法还没有执行。所以的字段都是零。,对象按照程序员的意愿进行初始化,这样一个对象,才算是真的完全的产生出来了。

 

对象的内存布局:

对象在内存存储的布局可以分为三块区域,:对象头,实例数据,填充对其

对象头可以分为两部分:

1:用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态,线程持有的锁,偏向线程ID,偏向时间戳等,被官方称为Mark Word。mark word 被设计为一个非固定结构的以便于存储更多的信息,

2:这部分就是存储类型指针了,指向对象的类元数据的指针,虚拟机通过这个指针来确定,对象是哪个类的实例。,当然了不是所有的对象头,存储这个指针,也就是说,不一定查找对象的元数据,不一定要通过对象本身。如果对象是数组的话,对象头,还需要有一块,记录数组长度的数据。因为虚拟机可以通过java对象元数据确定对象的大小,但是没法通过数组的元数据,确定数组的大小。

实例数据:是对象真正存储的有效信息,也是在 程序代码中定义的各种类型字段的内容,无论从父类继承的,还是在子类定义的,都需要记录。

填充对其:不是不然会存在的,只是满足8字节的整数倍,因为虚拟机的自动管理内存系统,要求,对象的起始地址必须是8自己的整数倍。起到占位符的作用,当实例数据部分没有对齐的话,就需要对其填充补齐。

 

对象的访问定位:建立对象是为了使用对象,对于栈上要访问堆上的对象,由于reference类型在虚拟机中,只规定了一个指向对象的引用,并没有规定这个引用如何去定位,访问堆中对象的具体位置。所以对象的访问方式取决于虚拟机的实现方式。目前有两种主流的方式,句柄方式和直接指针方式。

句柄方式:就是reference存放的是对象的句柄地址,指向在java堆上的句柄池中对应的对象,对象里包括了,对象的指针地址,和对象的类型数据的指针,然后分别指向了对中的实例对象数据和方法区中对象类型数据。

直接指针方式:直接指向对象的地址,然后在对象中,存放了对象的类型数据的指针,其指向了方法区对象的类型数据。

两种方式都有优点。

句柄的方式:可以达到在对象的地址变动的情况下,只变动实例对象的指针地址,不用变动reference的地址。

直接指针的方式:快啊,少了一步指针定位。

 

 

 

 

 

  相关解决方案