写点什么

☕【JVM 技术指南】「难点 - 核心 - 遗漏」TLAB 内存分配 + 锁的碰撞(技术串烧)!

发布于: 17 小时前
☕【JVM技术指南】「难点-核心-遗漏」TLAB内存分配+锁的碰撞(技术串烧)!

JVM 内存分配及申请过程

当使用 new 关键字或者其他任何方式进行创建一个类的对象时,JVM 虚拟机需要为该对象分配内存空间,而对象的大小在类加载完成后已经确定了,所以分配内存只需要在 Java 堆中划分出一块大小相等的内存,JVM 虚拟机中有指针碰撞和空闲列表两种方式分配内存。

指针碰撞方式

如果 Java 堆中内存是规整排列的,所有被用过的内存放一边,空闲的可用内存放一边,中间放置一个指针作为它们的分界点,在需要为新生对象分配内存的时候,只要将指针向空闲内存那边挪动一段与对象大小相等的距离即可分配。

代表 GC 回收器

ParNew,Serial,G1

空闲列表方式

如果 Java 堆中内存不是规整排列的,用过的内存和可用内存是相互交错的,这种情况下将不能使用指针碰撞方式分配内存,Java 虚拟机需要维护一个列表用于记录哪些内存是可用的,在为新生对象分配内存的时候,在列表中寻找一块足够大的内存分配,并更新列表上的记录。

代表 GC 回收器

cms

Java 虚拟机选择策略

Java 虚拟机采用哪种方式为新生对象分配内存,取决于所使用的垃圾收集器,当垃圾收集器具有整理过程时,虚拟机将采用指针碰撞的方式;当垃圾收集器的回收过程没有整理过程时,则采用空闲列表方式。

现在虚拟机栈进行分配

此部分属于两部分的分配机制,当 JVM 创建线程 Thread 对象:


  1. 直接分配:局部变量、形式参数表。

  2. 优化分配:逃逸分析(栈上分配、标量替换等功能)。


如果完成分配之后,则结束内存分配,否则出现分配失败,或者无法进行分配操作后,会进入堆内存方式的分配。

新生区-Eden 区的分配

TLAB 内存的分配策略

上面刚刚说过了,主要有两种内存分配机制:如果采用指针碰撞法,则会出现性能问题和指针分配冲突的问题.,JVM 虚拟机采用优化的手段,就是 TLAB(ThreadLocal Allocation Buffer)预先分配了内存块。

总体内存分配流程策略


如果 TLAB 内存分配失败或者空间不足,则 JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域,当 Eden 空间足够时,内存申请结束


当如果出现了 Eden 区内存无法进行分配,则会发生相关 MinorGC(JVM 试图释放在 Eden 中所有不活跃的对象(Minor GC),释放后若 Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区)。


此时 Survivor 区被用来作为 Eden 及 old 的中间交换区域,如果 Survivor 不足以放置 eden 区的对象 ,会进行担保分配,或者已经达到直接晋升到老年代的条件后,此时如果 old 区有空闲,Survivor 区的对象会被移到 Old 区。


当 old 区空间不够时,JVM 会在 old 区进行 major collection;


完全垃圾收集后,若 Survivor 及 old 区仍然无法存放从 Eden 复制过来的部分对象,导致 JVM 无法在 Eden 区为新对象创建内存区域,则出现"Out of memory 错误";


  1. jvm 优先分配在 eden 区

  2. 当 Eden 空间足够时,内存申请结束。



JVM 锁的膨胀执行流程机制

无锁节点/偏向锁阶段

创建线程的时候在程序执行到同步代码块的时候,首先会基于上面说到的内存分配策略进行分配内存,此时会当当前线程获取到了相关锁资源的时候,因为属于无锁状态下转换为偏向锁:

无锁标记头
偏向锁标记头


  1. 会将相关的当前线程的线程 ID 赋值到相关的标记字段中。

  2. 为了提高性能以及栈空间可以获取相关的竞争数据,会将对象头的标记字段(Markword)拷贝到栈空间内部(Lock Record)锁记录。



轻量级锁阶段

竞争到锁的线程

与此同时,当另外一个线程同时也去竞争该资源的时候,需要进行竞争,因为在获取资源的时候,底层采用 CAS 机制取获取相关的资源标志,一旦获取成功,便可以通过偏向锁标识进行判断是否属于当前的锁 owner 线程。


当发现不属于偏向锁的线程进来竞争的时候,此时会产生竞争关系,因为同一时刻,只能允许一个线程获取资源,当前获取资源的线程会因为有其他线程也争抢过该资源,故此将 java 对象头中的锁字段改为 00,如下图所示:

未竞争到锁的线程

当发生线程争抢 CAS 机制失败的时候,会进行相关的自旋机制,进行尝试下一次进行争抢到锁。



重量级锁阶段

未竞争到锁的线程
  1. 当超过自旋的线程一直处于自旋,且超过了自旋阈值之后,变会升级成为了重量级锁。或

  2. 当更多的线程都处于争抢状态且属于自旋锁机制之后(出现了大量的轻量级锁之后),便会升级未重量级锁。

  3. 直到被唤醒重新镜像竞争锁资源信息。


升级为重量级锁的结果会将线程的标志位置为 10


此时不会在进行自旋 CAS 争抢 ,而是直接阻塞执行(采用底层 mutex/Fast Mutex 锁进行暂停中断线程的执行)。

竞争到锁的线程
  • 当锁标记因为发生变化,成为了重量级锁,所以,线程会同步自己的所记录,发现不一致,同步为重量级锁状态后,释放锁之后,进行唤醒阻塞的状态的线程。

  • 锁的状态暂时处于重量级锁状态。接下来会专门写一篇文章讲解一下锁降级哦,锁降级会较为复杂,而且场景完全不一样,对 JVM 要求也不一样。

当对象出现消亡了回收状态:

发布于: 17 小时前阅读数: 5
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 “任何足够先进的技术都是魔法“

评论

发布
暂无评论
☕【JVM技术指南】「难点-核心-遗漏」TLAB内存分配+锁的碰撞(技术串烧)!