new了这么多对象,又有多少了解呢?
概述
Java是面向对象编程,在编程中使用关键字 new 来创建一个新的对象,在Java用object关键字表示。
每个Java开发都知道怎么创建和使用对象,但对对象的又有多少了解呢?
定义
这里说的对象不是找个男女朋友。
此对象 ≠彼对象
定义:对象是类的一个实例,对象具有状态和行为。
类定义成员变量和成员方法,可以通过对象名.成员变量来访问。
对象的结构
变量是申请内存来存储值,当创建变量的时候,需要申请内存空间。Java语言提供了八种基本类型,基本类型有对应的长度,如int 类型是4个字节(byte),也就是32位(bit)。
如果给一个上面的Java类,创建对象时在内存中是如何分配的?占了多少内存?
根据上面说的,内存大小应该是 int + Boolean + String = 4 + 1 + 4 = 9(bytes),但事实是这样吗?
对象的内存布局
从上面的图可以看出,在HotSpot虚拟机中,对象在内存中存储的布局可以分为:对象头(Header),实例数据(Instance Data)和对齐填充。
对象 = 对象头 + 实例数据 + 对齐填充
实例数据:对象真正存储的有效信息,存放类的属性数据信息,包括父类的属性信息;
对齐填充:虚拟机规范要求,对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
对象头:Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。
对象头
对象头主要包括:Mark Word(标记字段)、Klass Pointer(类型指针)和Array Length(当前对象是数组才有)。
Klass Pointer:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
Mark Word : 用于存储对象自身的运行时数据。
Array Length:数组长度(如果当前对象是数组)。
在Hotspot官网文档中的说明:
Mark word
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
32位JVM
64位JVM
锁标志位(lock):区分锁状态,01表示无锁或偏向锁,结合是否偏向就可区分是无锁状态还是偏向锁,0表示轻量级锁,1表示重量级锁,11表示GC。
biased_lock:是否偏向锁,由于无锁和偏向锁时锁标志位(lock)都是01,无法区分,需要加上是否偏向的标识。
分代年龄(age):表示对象GC次数,当次数到达阈值的时候,对象就会移到老年代。对象头中用4个bit位来保存分代年龄,所以GC分代年龄最大为15。
hashcode(hash):hash 字段用于存储对象的哈希码。当对象加锁后,计算的结果31位不够表示,在偏向锁,轻量锁,重量锁,hashcode会被转移到Monitor中。
偏向锁线程ID(JavaThread):偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。
epoch:偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。
ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争的时,JVM使用原子操作而不是OS互斥。这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的标题字中设置指向锁记录的指针。
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针。
GC mark 字段用于标记对象的回收状态。
Klass pointer
类型指针指向对象的类元数据,用于确定对象的类型信息和方法调用。这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。所以会默认开启指针压缩(如不开启 class pointer 将占用8字节)。
-XX:+UseCompressedClassPointers开启了类型指针压缩
-XX:+UseCompressedOops开启了普通对象指针压缩
Array Length
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。
64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。
Java 对象头的作用
决定对象的内存布局:Java 对象头的存在决定了对象的内存布局,包括对象头、实例数据和对齐填充。虚拟机利用对象头的信息来分配和管理内存,以及执行垃圾回收等操作。
确定对象的类型信息:Java 对象头中的类型指针指向对象的类元数据,虚拟机根据对象类型来决定如何对对象进行处理,包括对象的方法调用、属性访问等。
实现同步锁:Java 对象头中的 biased lock 和 GC mark 字段用于实现同步锁,包括偏向锁、轻量级锁和重量级锁等。虚拟机利用对象头中的同步锁信息来实现多线程同步和竞争控制,提高程序的并发性能。
实现垃圾回收:Java 对象头中的 GC mark 字段用于标记对象的回收状态,包括标记、清除、压缩等。虚拟机利用对象头中的 GC mark 信息来判断对象是否可达,以及是否需要回收内存。
总结
Java 对象头是 Java 堆内存中对象的一部分,用于存储对象的元数据。它由标记字和类型指针两部分组成,用于存储对象的状态信息、类元数据和同步锁信息等。Java 对象头在 Java 虚拟机中扮演着至关重要的角色,决定了对象的内存布局、类型信息、同步锁和垃圾回收等方面,对于程序的性能和稳定性有着重要的影响。
领取专属 10元无门槛券
私享最新 技术干货