对JAVA对象的初步理解
Java创建一个对象,通常仅仅一个“new”关键字,在虚拟机中,有很长一个过程:
1、虚拟机遇到一个new的指令时候,先去常量池检查该类是否被加载、解析、初始化过,没有,执行类的加载过程
2、执行该对象的static代码块(静态初始块)。(如果有的话,给Person.class类进行初始化)
3、在堆内存中开辟空间,分配内存地址
4、在堆内存中建立对象特有属性,并进行默认初始化
5、对属性进行显示初始化(声明成员属性并赋值)
6、执行普通初始块
7、执行构造函数
8、将内存地址赋值给栈内存中的jack变量
注意:
1、对象的类型数据存储在运行时常量池(方法区)
JVM说明new对象过程
1、检查类加载:去常量池定位指令参数是否执行过(获取类的全名称路径),并且类是否加载、解析和初始化过,没有,则先加载类,加载完毕之后,详细过程分为
加载、验证、准备、解析、初始化
(1)加载是类加载的一段过程:将源文件的class文件找到类的信息将其加载到方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口
详细描述:根据类的全限定名获取到该类的二进制流,该二进制流到方法区,在内存中生成代表这个类的java.lang.class对象(虽然是个对象,但是比较特殊,存储在方法区),作为方法区该类的入口。
(2)验证:为了确保class文件的字节流中不存在对jvm有害的信息,并且符合jvm的要求。包括文件格式、元数据、字节码、符号的验证
(3)准备:在方法去进行,正式为类分配内存和并设置类变量的统一赋初始值,比如,static int a = 0;
(4)解析:将常量池的符号(class文件应用一组符号指代锁引用的目标)引用替换为直接引用(指向目标的指针、偏移量、间接定位目标的句柄)
(5)初始化:执行类定义中的程序代码(字节码)。在准备阶段,变量进行过系统的统一赋初始值,此阶段,才开始进行执行程序员制定的初始化。由执行的类加载器<clinit>()执行。
2、为变量分配堆空间
类加载完成之后,需要在堆中为类对象分配空间,所需要的空间大小在类加载完成之后已经确定。分配内存的方式:
(1)指针碰撞:假设内存是连续规整的,左边使用过的,右边未使用的,中间是一个指针,分配内存就是将指针向空闲那边挪动一段与对象大小相同的距离,这种分配的方式称为指针碰撞
(2)空闲列表:假设内存不规整,已使用的和未使用的交错,则必须维护一张链表记录哪些是可用的
分配堆内存线程安全方法
(1)同步处理:CAS失败重试
(2)按照线程划分在不同的空间之中:每个线程预先分配一小块堆内存,作为本地线程分配缓冲(Thread Local Allocation Buffer TLAB),线程需要分配内存,先在它的TLAB上分配,TLAB用完之后,才需要同步锁定
3、内存空间初始化和对象头的设置
内存空间全部初始化为零值(不包括对象头),如果使用TLAB,可以预先设置,为了保证对象的实例字段,在不赋值就可以直接使用,访问到对象的对应零值
对象头设置:
头中包括对象的属于那个类的实例、对象的元数据信息(java用来描述数据的数据)、对象的哈希码、对象的GC分代等
4、类初始化
通过init方法调用类的构造函数,对类对象进行初始化
java 对象的内存布局
Java对象由三部分构成:对象头、实例数据、对齐补充
(1)对象头:
第一部分是与对象在运行时状态相关的信息,长度通过与操作系统的位数保持一致。包括对象的哈希值、GC分代年龄、锁状态以及偏向线程的ID等。由于对象头信息是与对象所定义的信息无关的数据,所以使用了非固定的数据结构,以便存储更多的信息,实现空间复用。因此对象在不同的状态下对象头的存储信息有所差别。
另一部分是类型指针,即指向该对象所属类元数据的指针,虚拟机通常通过这个指针来确定该对象所属的类型(但并不是唯一方式)。
另外,如果对象是一个数组,在对象头中还应该有一块记录数组长度的数据,因为JVM可以通过对象的元数据确定对象的大小,但不能通过元数据确定数组的长度。
(2)实例数据
实例数据存储的是真正的有效数据,即各个字段的值。无论是子类中定义的,还是从父类继承下来的都需要记录。这部分数据的存储顺序受到虚拟机的分配策略以及字段在类中的定义顺序的影响。
(3)对齐补充
这部分数据不是必然存在的,因为对象的大小总是8字节的整数倍,该数据仅用于补齐实例数据部分不足整数倍的部分,充当占位符的作用。
java 对象的访问定位
Java程序需要通过栈上的reference数据来操作堆上的具体对象,这是众所周知的。但是由于reference类型在Java虚拟机的规范中只规定了一个指向对象的引用,并没有定义这个引用该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。
那么该怎么去访问对象呢?
目前主流的访问方式有两种,分别是:句柄和直接指针。
(1)句柄:Java堆中划分出一块内存来当做句柄池。有了句柄,reference存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
优点:对象的移动,只需要修改句柄池就可以
缺点:增加了内存开销
(2)直接指针访问:如果使用直接指针访问,那么Java堆对象的布局中就必须要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
优点:减小了一次指针的开销,Java 中对象访问的很频繁,这个开销节省的也很客观
缺点:对象的移动,修改起来也麻烦
Hot Spot 使用的是指针直接访问的