JVM对象的加载流程
类加载检查
当字节码加载引擎遇到new指令时,会检查常量池中是否包含该类的符号引用,并检查该符号引用代表的类是否已被类加载(加载,验证,准备,解析,初始化),未进行类加载,将先进行类加载。
new 指令 在代码上体现为new关键字、对象克隆、对象序列化等等
内存分配
在经过是否类加载的检查后,将JVM为新的对象进行分配内存,对象所需要的内存大小,在类加载完成后可以确认,即为对象在堆中的新生代中伊甸园区中划分出给定大小的内存。
- 划分内存的问题
- 如何划分内存
- 在并发情况下,如何处理在给A对象分配内存时,内存分配指针还未修改,B对象又使用该指针进行分配内存情况(即一块内存被俩个对象争抢).
划分内存方法
- 指针碰撞
Bump the Pointer
(默认使用)只是绝大多数可能使用失败
在假设JVM堆中的内存是绝对规整的,实现堆的数据组中已使用指针指向的前段数组都被使用了,那所需要的内存只需要在将指针向后进行挪动与对象大小相等大小的空间即可.
- 空闲列表
Free List
假设JVM堆中的内存不是规整的,即已分配的内存与未分配的内存相互交错,无法简单的进行指针挪动达到效果,这时JVM维护一张表用于记录(猜测为堆结构),使用堆结构查找一块足够大的空间划分给对象实例,并对堆已划分的进行删除。
解决并发方法
- CAS(compare and swap)
JVM采用CAS与失败重试机制保证更新操作的原子性,从而对分配内存操作进行同步处理。
- 本地线程分配缓冲(Thread Local Allcation Buffer,TLAB)(默认开启)
将内存的划分根据线程的不同分别在不同的位置进行划分,由于在不同的空间进行处理互不干扰避免了并发问题,即每个线程在JVM堆中预先分配一小块内存,可以通过
XX:+/-UseTLAB
(+/-表示+或-)JVM参数来设置是否使用TLAB(用于关闭
),XX:TLABSize 指定TLAB大小。
初始化
在内存分配完毕后,JVM需要将分配的内存空间进行初始化为零值(
不包括对象头
),在使用TLAB,该过程提前在TLAB分配时进行。该操作保证了对象在实例字段在JAVA代码中可以不赋予初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。(这里指的就是基本数据类型
)
设置对象头
初始化后,JVM需要堆对象进行必要的设置,即标识这个对象是那个类的实例,类的元数据信息位置,对象的哈希码,对象的GC年龄等。而这些信息都存储在对象的对象头
Object Header
中。
对象存储布局
在HotSpot(JVM)虚拟机中,对象分为三个区域(对象头、实例数据、对其填充)
- 对象头
Header
而对象头又分为两部分信息
- 存储对象自身运行时数据
mark word
哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。
- 类型指针
Klass Pointer
对象用来存储
指向类元数据的指针
,虚拟机可以通过该指针确定本对象是那个类的实例。
- 存储对象自身运行时数据
执行< init >方法
执行< init >方法,即JAVA代码的初始化,即对象按照构造方法与基本数据初始赋值(非静态初始值)。
补充
对象头详细与总览
其中jol-core进行查看对象头
使用64机器,并无数组情况下
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header)01 00 00 00 (00000001 00000000 00000000 00000000)(1) //mark word
4 4 (object header)00 00 00 00 (00000000 00000000 00000000 00000000)(0) //mark word
8 4 (object header)e5 01 00 f8(11100101 00000001 00000000 11111000) (‐134217243) //Klass Pointer
12 4 (loss due to the next object alignment) //数据对齐16字节,优化存储
Instance size:16 bytes
Space losses:0 bytes internal +4 bytes external = 4 bytes total
在对齐16字节情况下,数据有序存储,可提高查找效率,避免数据存储在交错空间内的查找性能消耗。
在64机器下,使用数组
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header)01 00 00 00 (00000001 00000000 00000000 00000000)(1)//mark word
4 4 (object header)00 00 00 00 (00000000 00000000 00000000 00000000)(0)//mark word
8 4 (object header)6d 01 00 f8(01101101 00000001 00000000 11111000) (‐134217363) //Klass Pointer
12 4 (object header)00 00 00 00 (00000000 00000000 00000000 00000000)(0)//数组
16 0 int [I.<elements>N / A
Instance size:16 bytes
Space losses:0 bytes internal +0 bytes external = 0 bytes total
在使用数组情况下,将会占用4个字节存储数组。
在存在实体数据的情况下(new了一个对象)
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 61 cc 00 f8 (01100001 11001100 00000000 11111000) (‐134165407)
12 4 int A.id 0
16 1 byte A.b 0
17 3 (alignment/padding gap)
20 4 java.lang.String A.name null
24 4 java.lang.Object A.o null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
指针压缩
在jdk1.6版本后开始正常,在64位系统JVM支持指针压缩
- 配置方法
- jvm配置:
- 开启指针压缩:XX:+UseCompressedOops(默认开启),关闭指针压缩:XX:UseCompressedOops
- UseCompressedOops,compressed(压缩)、oop(ordinary object pointer)对象指针
- jvm配置:
原因:
- 减少在64位平台的内存消耗,在64位平台中HotSpot使用64位指针,内存使用会多出1.5倍左右,使用大指针在主内存和缓存之间移动数据, 占用较大宽带,同时GC也会承受较大压力。
- 32位地址足够使用,32位地址可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm 只用32位地址就可以支持较大的内存配置(小于等于32G)
注意
- 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
- .堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内 存不要大于32G为好