90% 的 Java 程序员都说不上来的为什么 Java 代码越执行越快(2)- TLAB预热

常常听到 Java 性能不如 C/C++ 的言论,也常常据说 Java 程序须要预热,那么其中主要缘由是啥呢面试

面试的时候谈到 JVM,也有不少面试官喜欢问,为啥 Java 程序越执行越快呢算法

通常人都能回答上来,类加载,缓存预热等等,可是深刻下去,最重要的却没有答上来,今天本系列文章就来帮助你们理解这个问题的关键。本篇文章是 TLAB 预热。缓存

TLAB(Thread Local Allocation Buffer)线程本地分配缓存区,这是一个线程专用的内存分配区域。安全

image

既然是一个内存分配区域,咱们就先要搞清楚 Java 内存大概是如何分配的。markdown

image

咱们这里不考虑栈上分配,这些会在 JIT 的章节详细分析,咱们这里考虑的是没法栈上分配须要共享的对象多线程

对于 HotSpot JVM 实现,全部的 GC 算法的实现都是一种对于堆内存的管理,也就是都实现了一种堆的抽象,它们都实现了接口 CollectedHeap。当分配一个对象堆内存空间时,在 CollectedHeap 上首先都会检查是否启用了 TLAB,若是启用了,则会尝试 TLAB 分配;若是当前线程的 TLAB 大小足够,那么从线程当前的 TLAB 中分配;若是不够,可是当前 TLAB 剩余空间小于最大浪费空间限制(这是一个动态的值,咱们后面会详细分析),则从堆上(通常是 Eden 区) 从新申请一个新的 TLAB 进行分配。不然,直接在 TLAB 外进行分配。TLAB 外的分配策略,不一样的 GC 算法不一样。例如G1:性能

  • 若是是 Humongous 对象(对象在超过 Region 一半大小的时候),直接在 Humongous 区域分配(老年代的连续区域)。
  • 根据 Mutator 情况在当前分配下标的 Region 内分配

这里,咱们先只关心 TLAB 分配。 对于单线程应用,每次分配内存,会记录上次分配对象内存地址末尾的指针,以后分配对象会从这个指针开始检索分配。这个机制叫作 bump-the-pointer (撞针)。 对于多线程应用来讲,内存分配须要考虑线程安全。最直接的想法就是经过全局锁,可是这个性能会不好。为了优化这个性能,咱们考虑能够每一个线程分配一个线程本地私有的内存池,而后采用 bump-the-pointer 机制进行内存分配。这个线程本地私有的内存池,就是 TLAB。只有 TLAB 满了,再去申请内存的时候,须要扩充 TLAB 或者使用新的 TLAB,这时候才须要锁。这样大大减小了锁使用。优化

TLAB 初始化

image

TLAB 分配

image

GC 时 TLAB 回收与重计算指望大小

image

为什么 Java 代码越执行越快 - TLAB预热

根据以前的分析,每一个线程的 TLAB 的大小,会根据线程分配的特性,不断变化并趋于稳定,大小主要是由分配比例 EMA 决定,可是这个采集是须要必定运行次数的。而且 EMA 的前 100 次采集默认是不够稳定的,因此 TLAB 大小也在程序一开始的时候变化频繁。当程序线程趋于稳定,运行一段时间后, 每一个线程 TLAB 大小也会趋于稳定而且调整到最适合这个线程对象分配特性的大小。这样,就更接近最理想的只有 Eden 区满了才会 GC,全部 Eden 区的对象都是经过 TLAB 分配的高效分配状况。这就是 Java 代码越执行越快在 TLAB 方面的缘由。spa

相关文章
相关标签/搜索