目录
-
-
- 一. JVM对象创建过程详解
-
- 1. 类加载检查
- 2. 分配内存
-
- 2.1 如何划分内存?
- 2.2 并发问题
- 3. 初始化
- 4. 设置对象头
- 5. 执行<init>方法
- 二. 对象头和指针压缩详解
- 三. JVM对象内存分配详解
- 四.逃逸分析 & 栈上分配 & 标量替换详解
-
- 1. 逃逸分析 & 栈上分配
- 2. 标量替换
- 3. 标量与聚合量
- 4. 对象在堆内存中的流转与分配
- 五.对象内存回收机制详解
-
- ★ 1. 如何判断对象是可回收的?
- ★ 2. 常见的引用类型
- ★ 3. 对象真正被GC回收的两次标记过程详解
- ★ 4. 如何判断一个类是无用的类?
-
一. JVM对象创建过程详解
1. 类加载检查
当虚拟机遇到一条new指令时,首先会先检查这个指令的参数是否能在常量池
中定位到一个类的符号引用
,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,必须先执行相应的类加载过程
。
2. 分配内存
在类检查通过之后,虚拟机会给新生对象分配内存
。对象所需要的内存大小在类加载完成之后就可以确定,为对象分配空间的任务等同于把一块确定的内存从JVM堆
中划分出来。
2.1 如何划分内存?
-
指针碰撞
(Bump the Pointer): 默认使用; 如果JVM堆中的内存是绝对规整的,所有用过的内存在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,那所分配内存就是将指针向空闲空间那边挪动一段与对象大小相等的距离
-
空闲列表
(Free List):JVM堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,此时就无法使用指针碰撞了,JVM就必须维护一个空闲内存的列表,记录堆中哪些位置是可用的,在分配内存时,在列表中分配一块足够大的空间给对象实例,并且更新列表上的记录。
★ 为啥默认使用指针碰撞方式?- 空闲列表中,已使用的空间是
无规则排列
的,并且未使用的内存空间的大小是不一致的,当一个对象实例需要存储的时候,就需要先去空闲列表中找一个和实例大小相匹配的内存空间,并且还需要更新空闲列表。 - 空闲列表,无法将内存空间使用率最大化。
- 空闲列表中,已使用的空间是
2.2 并发问题
-
如何产生?
-
指针碰撞
:当给对象A分配内存时,指针位置还未及时修改,此时对象B也使用原来的指针来分配内存空间,俗称抢内存。 -
空闲列表
:当给对象A分配内存时,在空闲列表寻找合适对象A的内存空间,如果此时找到一块内存位置,还未及时存入对象A,空闲列表也未做更新,对象B也通过空闲列表找到同一块内存位置,此时就会出现两个对象争抢同一块内存空间的现象。
-
-
解决办法?
-
CAS
(Compare And Swap):比较与交换
,是实现多线程同步的原子指令,将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容更新为给定值。JVM虚拟机采用CAS并且配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。 -
TLAB
(Thread Local Allocation Buffer):线程本地分配缓存区
,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存 。
-
3. 初始化
内存分配完成后,JVM虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)(如果使用TLAB,此步骤也可以提前至TLAB分配时进行),保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段类型所对应的零值。
4. 设置对象头
★ 初始化零值之后,虚拟机需要对对象进行的必要的设置,例如:这个对象是哪个类的实例,如何才能找到类的元数据信息
,对象的哈希码值
,对象的GC分代年龄
等信息,这些信息存放在对象的对象头Object Heade
r中。
★ 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头
(Header)、实例数据
(Instance Data)和对齐填充
(Padding)。
★ HotSpot虚拟机的对象头包括 两部分信息
第一部分用于存储对象自身的运行时数据,如哈希码
(HashCode)、GC分代年龄
、锁状态标志
、线程持有的锁
、偏向线程ID
、偏向时间戳
等。
另外一部分是类型指针
,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
5. 执行方法
即对象按照程序的编码进行初始化,也就是属性赋值
(此处赋值,并非赋零值,而是真实的程序编码赋予的值)和执行构造方法
。
二. 对象头和指针压缩详解
文章来源:https://uudwc.com/A/DNLjj
-
Mark word
是一种用于对象头部的标记
,它记录了对象的元数据信息和运行时状态。
在JVM中,每个对象都有一个对象头部,用于描述对象的元数据信息和运行时状态。其中,mark word记录了对象的锁状态、GC状态以及其他一些标志位信息。它可以被用于多种用途,如实现线程安全、对象的同步和对象的标记-清除等垃圾回收算法。在64位JVM中,mark word占据了8字节的空间,可以存储更多的信息,因此可以提高JVM的性能。 -
Klass pointe
: 是指向对象类元数据的指针
,在64位JVM中,klass pointer占据了4字节的空间。
每个Java对象都有一个klass pointer,它指向该对象所属的类的元数据。元数据描述了该类的所有属性,方法和其他信息。klass pointer也被用于确定对象的大小和布局,以便在内存中分配对象时可以正确地分配空间。
模拟: 对象大小和指针压缩(代码如下)文章来源地址https://uudwc.com/A/DNLjj
- 首先需要在项目的pom文件中依赖jol-core包
<!-- 可以明细jvm中的对象大小 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
<