乐趣区

关于java:清华博士教你如何快速了解JVM虚拟机码农天才就是不一样

上一篇是分享的是《Thread 详解》,这篇给大家分享《JVM 虚拟机 - 理解 Java 堆中对象调配、布局和拜访的全过程》,对象是 Java 中最常被提到的概念,也是 Java 的灵魂,Java 中所有皆是对象。JVM 是 Java 的根基,了解灵魂与根基是如何交融的,对于了解 Java 自身至关重要。

对象的创立

对象的创立从 Java 语言层面上,往上往就是一个简略的 new 即可搞定,而深刻 JVM 的底层,可就简单得多了。

JVM 中对象的创立过程,分为如下 5 步,如图:

类加载查看

虚拟机遇到一条 new 指令后,会执行如下几个步骤:

首先将去查看这个指令的参数是否能在常量池中定位到这个类的符号援用。

查看这个符号援用代表的类是否已被加载过、解析过、初始化过。

如果没有,那必须先执行相应的类加载过程。

确定对象所需的内存大小。

类加载查看通过后,进入下一步骤,为新生对象分配内存。

分配内存

为对象调配空间相当于分田地,就是将空余的内存划分一块给对象。

因为不同的 JVM 虚拟机,Java 堆的内存规整状况是不同的,所以调配形式上也会有肯定区别。

Java 堆是否规整,由 JVM 所采纳的垃圾收集器是否带有压缩整顿性能决定的

内存空间调配形式

指针碰撞

Java 堆中内存是规整(无内存碎片)时应用,即垃圾收集器有压缩整顿性能时。

原理就是将用过的内存放在一块,没用的放一块,两头用指针做标识,调配的时候将指针向闲暇内存区域挪动。

闲暇列表

Java 堆中内存不规整,即垃圾收集器无压缩整顿性能时。

应用内存和闲暇内存互相交织,JVM 需保护一个列表,记录上哪些内存是可用的,在调配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

两者具像化出现如图:

并发时的内存调配

创建对象是很频繁的事,并发创建对象的时候有一个很重要的问题,就是线程平安。

如:程序中创建对象 A 和对象 B,底层 VM 给 A 对象分配内存,指针没来及批改,对象 B 同时应用原来的指针分配内存。

JVM 个别采纳以下两种形式来保障线程平安。

同步解决:CAS

CAS 是乐观锁的一种实现形式。

所谓乐观锁就是,每次不加锁而是假如没有抵触而去实现某项操作,如果因为抵触失败就重试,直到胜利为止。

虚拟机采纳 CAS 配上失败重试的形式保障更新操作的原子性。

本地线程调配缓冲:TLAB

为每一个线程事后在 Eden 区调配一块儿内存,即为 TLAB

JVM 在给线程中的对象分配内存时,首先在 TLAB 调配

当对象大于 TLAB 中的残余内存不足或已用尽时,再采纳上述的 CAS 进行内存调配

更多对于 CAS 的内容能够参考博主的另一篇文章 Java 并发 / 多线程 -CAS 原理剖析

初始化零值

内存调配实现后,虚拟机须要将调配到的内存空间都初始化为零值(不包含对象头)

这一步操作保障了对象的实例字段在 Java 代码中能够不赋初始值就间接应用,程序能拜访到这些字段的数据类型所对应的零值。

设置对象头

对象头是虚拟机对对象进行必要的设置

对象头寄存了对于对象的各种信息如:

对象是哪个类的实例

如何能力找到类的元数据信息

对象的哈希码

对象的 GC 分代年龄

……

依据虚拟机以后运行状态的不同,如是否启用偏差锁等,对象头会有不同的设置形式。

执行 init 办法

在下面工作都实现之后,从虚拟机的视角来看,一个新的对象曾经产生

从 Java 程序的视角来看,对象创立才刚开始,<init> 办法还没有执行,所有的字段都还为零。

所以一般来说,执行 new 指令之后会接着执行 <init> 办法,把对象依照程序员的志愿进行初始化,这样一个真正可用的对象才算齐全产生进去。

对象的内存布局

在 Hotspot 虚拟机中,对象在内存中的布局能够分为 3 块区域。

对象头

对象头下面曾经介绍过了,其实次要就是分为两类:

用于存储对象本身的运行时数据

类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

实例数据

这部分是对象真正存储的无效信息,也是在程序中所定义的各种类型的字段内容。

对齐填充

对齐填充不是必然存在的,没有实际意义,就是用来补位的。

因为 Hotspot 虚拟机的主动内存管理系统要求对象起始地址必须是 8 字节的整数倍,对象的大小必须是 8 字节的整数倍。

所以,当对象实例数据局部没有对齐时,就须要通过对齐填充来补全。

对象的拜访定位

建设对象就是为了应用对象,咱们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。

对象的拜访形式由虚拟机实现而定,目前支流的拜访形式有如下两种形式

应用句柄

Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中蕴含了对象实例数据与类型数据各自的具体地址信息,如图:

间接指针

如果应用间接指针拜访,那么 Java 对对象的布局中就必须思考如何搁置拜访类型数据的相干信息,而 reference 中存储的间接就是对象的地址。

二者比照

应用句柄来拜访的最大益处是 reference 中存储的是稳固的句柄地址,在对象被挪动时只会扭转句柄中的实例数据指针,而 reference 自身不须要批改。

应用间接指针拜访形式最大的益处就是速度快,它节俭了一次指针定位的工夫开销。

  • 以上就是《JVM 虚拟机 - 理解 Java 堆中对象调配、布局和拜访的全过程》的分享。
  • 也欢送大家交换探讨,该文章若有不正确的中央,心愿大家多多包涵。
  • 你们的反对就是我最大的能源,如果对大家有帮忙给个赞哦~~~
退出移动版