11 对象创建
类加载
空间分配
指针碰撞
空闲列表
TLAB 线程本地缓冲区
TLAB 是 Thread Local Allocation Buffer 的缩写,也称为线程本地分配缓冲区。我们以垃圾回收器采用 CMS 为例,这种情况下,所有的对象都是在新生代 Eden 区分配的。因为 Eden 区也是全局共享的,当同一时间有多个线程同时向 Eden 区申请空间分配的时候,就必然会引发同步问题,从而导致内存分配效率降低。
为此 JVM 引入了 TLAB 机制,也就是以线程为单位,为每个线程分配一块区域以减少同步竞争,从而提升对象分配的效率
当然即使使用了 TLAB,也不会无限地给一个线程分配空间,因为在实际的生产环境中堆空间是非常宝贵的资源,所以 TLAB 分配的空间一般不会很大。我们可以通过 -XX:TLABWasteTargetPercent 来设置 TLAB 占用的空间大小。因为 TLAB 空间是有限的,所以很可能会出现分配的对象大小超过 TLAB 空间的情况。这种情况下有两种解决方案,第一种就是绕过 TLAB,直接在堆上分配,第二种就是废弃掉当前的 TLAB,创建一个新的 TLAB 来满足新分配的对象的空间要求。
对象创建两段论
对象创建快捷流水线
这条全新的流水线主要由四个节点构成
12 JVM对象创建模式和最佳实践
JVM创建对象流程
对象在 JVM 中存在的形态
Book 类对象存储在 JVM 里,总共由 3 个部分组成,分别是对象头、实例数据和对齐填充
我们可以用 -XX:+UseTLAB 来启用 TLAB,它在 JDK8 中是默认开启的。开启 TLAB 后,每个线程在创建对象时都会在自己的 TLAB 中进行,从而避免了不同线程间的内存分配竞争,提升了系统性能。反之,如果我们没有开启 TLAB,那么这 10 个线程都将在共有的 Eden 区进行内存分配,会存在较大的竞争和同步开销,系统性能将大大降低。
13 对象回收(上):定位待回收的对象
什么是 GC
GC 是 Garbage Collection 的缩写,中文叫做垃圾回收。它是一种自动化的内存管理机制,能够识别和回收不再使用的内存空间。在 Java 里,JVM 负责 GC。通过这种方式,Java 程序员无需手动进行内存的分配和回收,从而降低了内存泄漏的风险,提高了开发效率。
GC 的主要目标
GC 的主要步骤
14 对象回收(下):垃圾回收算法
15 G1垃圾回收算法
JDK 11 及以上的版本中,G1 一骑绝尘,使用率占比达到 65% 。所以在这个 CMS 垃圾收集器逐步淡出历史舞台,而 ZGC 还未完全成熟的阶段,G1 垃圾收器注定将在未来的一段时间内扛起主流垃圾收集器的大旗,成为兼顾延迟与吞吐的最佳选择。而现实中,在 JDK 11 逐渐登上历史舞台的背景下,越来越多的系统,通过升级到 G1 以实现性能的提升
G1 内存划分
G1 中的年轻代、老年代是一种逻辑划分,而不再是物理划分,也就是说年轻代和老年代不再是连续的内存地址空间,而是一组 Region 的集合。这里的 Region 是 G1 中最小内存分配单元,你可以通过 -XX:G1HeapReginotallow=n 来设置 Region 的大小,可以设定为 1M、2M、4M、8M、16M、32M。
JVM 中规定 Region 的大小必须是 2 的整数幂,并且不能超过 32M。如下图所示,当你采用 G1 作为垃圾收集器时,JVM 会将堆内存划分为一个个大小相同的 Region 进行管理。所以合理设置 Region 的大小,是用好 G1 的一个非常重要的因素
G1 采用的是分代收集和区域(Region)划分相结合的垃圾回收算法。在最小的物理内存单元是 Region 的基础上,依据分代收集算法,G1 将堆内存在逻辑层面划分为 4 个区域,分别是 Eden Space、Survivor Space、Old Generation 以及 Humongous Space。Eden Space 与 Survivor Space 的组合,就是我们通常说的年轻代,而 Humongous Space 是 G1 专门提出来的一个区域,用于专门存储程序中的大对象。
一个对象的大小如果超过了 Region 容量的一半,就会被直接存放至 Humongous 区域。当对象的大小远超 Region 大小的时候,JVM 会分配连续的 Humongous Region 用来存放这个对象。
基于上面提到的内存划分和垃圾回收算法,在 G1 中存在三种 GC 模式,分别是 Young GC、Mixed GC 和 Full GC。
定位一个待回收对象
G1 定位待回收对象的时候,还有两个重要的工具,分别是 Remembered Set(记忆集)和 Card Table(卡表)
Remembered Set(记忆集)
每个 Region 都有对应的 Remember Set。主要记录某个 Region 里的对象被别的 Region 的对象引用的情况。这样,当开始扫描时,只需要将某个 Region 的 GC roots set 中加上 remember set,就能保证没有遗漏。
Card Table(卡表)
Card Table 也是 G1 用于处理跨 Region 引用的一种方式,主要应用在并发标记阶段。Java 堆被分为很多小块,也叫做 Card(卡片),每张卡片默认大小就是 512Byte,每张卡记录了从某个地址开始连续 512 字节内存范围的变化情况。当在写入对象引用的时候,JVM 会把含有这个引用的卡标记为“dirty”,如果卡中的对象发生了改变,那么对应的卡就会被标记,这样在进行垃圾回收的时候,可以找出可能的跨区引用,避免引用丢失。
Remembered Set 和卡表的主要作用就是为了处理和跟踪跨 Region 引用关系,避免在进行局部垃圾回收时,出现引用丢失导致应用出错。这两个概念的使用无需程序员关心,都是由 JVM 和 G1 收集器自动完成的。但是正是借助记忆集和卡表的功能,G1 会在运行期间计算出每个 Region 回收的性价比。从而借助停顿预测模型,找出用户预期时间内最高回收收益的 Region 组合。
16 面向未来的垃圾回收 ZGC Shenandoah
ZGC 的由来
Oracle 决定开发一种全新的垃圾收集器,旨在提供低停顿时间、高可伸缩性和适用于大内存应用程序的解决方案。于是在 2017 年,Oracle 推出了名为 ZGC 的垃圾收集器,首先作为 Oracle JDK 的一个实验性功能引入。在随后的几个版本中,Oracle 对 ZGC 进行了改进和优化,不断增加新功能,提升性能,并解决了一些问题。最终,ZGC 在 JDK 11 中正式成为 Oracle JDK 的一部分,摇身一变,成为了一种稳定的垃圾收集器,并提供商业支持
ZGC 使用了读屏障技术来捕获引用关系的变化。除此之外,ZGC 也具有支持大型堆内存的功能,最高可达 16TB 的堆内存容量。这对于需要处理大量数据的应用程序非常有用。
ZGC 是一种专为高并发和大内存应用场景设计的垃圾收集器,具备可伸缩性、低停顿时间、大堆内存支持和易用性等特点
ZGC VS G1
ZGC的黑科技:颜色指针
ZGC的垃圾回收过程
ZGC的最佳实践
Shenandoah 垃圾回收器
17 JVM扩展机制
在 Java 中,常见的扩展机制有接口、抽象类、反射、SPI 等
允许我们在不改变原有代码的基础上进行功能的扩展
扩展机制的实现方式是多样的,接口、抽象类都可以用于定义公共的方法,供其他类实现或者继承;反射则可以在运行时动态地创建对象和调用方法;动态代理则可以在运行时动态地创建和控制对象的行为;SPI 则是 Java 提供的一种服务提供发现机制,它允许第三方为一个产品或者一个服务提供插件
接口和抽象类
接口是 Java 里的一种核心机制,它主要负责定义对象的行为协议。这里的协议,你可以理解成规定的一种标准,或者说是一种约束。接口就是规定了一种方法的集合,而这些方法如何实现,就由实现接口的类来决定。接口在 Java 里的特殊之处在于,一个类可以实现多个接口,这就有力地支持了 Java 的动态特性。