常常听到 Java 性能不如 C/C++ 的言论,也常常据说 Java 程序须要预热,那么其中主要缘由是啥呢?面试
面试的时候谈到 JVM,也有不少面试官喜欢问,为啥 Java 程序越执行越快呢?算法
通常人都能回答上来,类加载,缓存预热等等,可是深刻下去,最重要的却没有答上来,今天本系列文章就来帮助你们理解这个问题的关键。本篇文章是 TLAB 预热。缓存
TLAB(Thread Local Allocation Buffer)线程本地分配缓存区,这是一个线程专用的内存分配区域。安全
既然是一个内存分配区域,咱们就先要搞清楚 Java 内存大概是如何分配的。markdown
咱们这里不考虑栈上分配,这些会在 JIT 的章节详细分析,咱们这里考虑的是没法栈上分配须要共享的对象。多线程
对于 HotSpot JVM 实现,全部的 GC 算法的实现都是一种对于堆内存的管理,也就是都实现了一种堆的抽象,它们都实现了接口 CollectedHeap。当分配一个对象堆内存空间时,在 CollectedHeap 上首先都会检查是否启用了 TLAB,若是启用了,则会尝试 TLAB 分配;若是当前线程的 TLAB 大小足够,那么从线程当前的 TLAB 中分配;若是不够,可是当前 TLAB 剩余空间小于最大浪费空间限制(这是一个动态的值,咱们后面会详细分析),则从堆上(通常是 Eden 区) 从新申请一个新的 TLAB 进行分配。不然,直接在 TLAB 外进行分配。TLAB 外的分配策略,不一样的 GC 算法不一样。例如G1:性能
这里,咱们先只关心 TLAB 分配。 对于单线程应用,每次分配内存,会记录上次分配对象内存地址末尾的指针,以后分配对象会从这个指针开始检索分配。这个机制叫作 bump-the-pointer (撞针)。 对于多线程应用来讲,内存分配须要考虑线程安全。最直接的想法就是经过全局锁,可是这个性能会不好。为了优化这个性能,咱们考虑能够每一个线程分配一个线程本地私有的内存池,而后采用 bump-the-pointer 机制进行内存分配。这个线程本地私有的内存池,就是 TLAB。只有 TLAB 满了,再去申请内存的时候,须要扩充 TLAB 或者使用新的 TLAB,这时候才须要锁。这样大大减小了锁使用。优化
根据以前的分析,每一个线程的 TLAB 的大小,会根据线程分配的特性,不断变化并趋于稳定,大小主要是由分配比例 EMA 决定,可是这个采集是须要必定运行次数的。而且 EMA 的前 100 次采集默认是不够稳定的,因此 TLAB 大小也在程序一开始的时候变化频繁。当程序线程趋于稳定,运行一段时间后, 每一个线程 TLAB 大小也会趋于稳定而且调整到最适合这个线程对象分配特性的大小。这样,就更接近最理想的只有 Eden 区满了才会 GC,全部 Eden 区的对象都是经过 TLAB 分配的高效分配状况。这就是 Java 代码越执行越快在 TLAB 方面的缘由。spa