您的位置:  首页 > 技术 > java语言 > 正文

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

2021-09-02 11:00 https://my.oschina.net/liboware/blog/5217064 李浩宇Alex 次阅读 条评论

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要求也不一样。

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

  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接