栈上分配和TLAB的区别

栈上分配是JVM的一种优化策略,用于提升对象创建和清理效率,减少GC负担。当对象无逃逸现象时,JIT编译器会进行标量替换,直接在栈帧中处理。TLAB(本地线程分配缓冲)和PLAB(晋升本地分配缓存)是为了解决多线程在堆上创建对象的同步问题,提高内存分配效率,它们分别在Eden区和Survivor、老年代为线程提供专有分配空间,确保小对象快速创建,降低并发冲突。

栈上分配

JVM中,栈上空间为线程私有,堆上空间为全局共享。所以大部分对象存在于堆上,线程通过栈上的引用指向堆上对象的内存地址。堆上没有任何引用关系的对象会被JVM标记后GC掉。

很多对象不存在逃逸现象,即线程专有,那么从栈到堆的寻址过程就显得有些多余了,而且还需要等到GC时才能清理该对象,不如直接将该对象创建在线程自身的栈帧中,随着线程销毁直接清除,这就叫栈上分配。JDK7之后,栈上分配的对象会被JVM打散成一个一个的原始类型,这些原始类型就叫做“标量”,这个行为叫做标量替换。每个标量可以被单独的分析和优化,这样就无需再创建原始对象了,即节省了创建对象的成本,又避免了对象清理带来的GC。栈上分配和标量替换都是JIT的优化机制,更多可见:JIT编译器的优化方式

举个例子,通过语文和英语考试分数相加计算总分:

class Score {
  int chinese;
  int english;
}

public int calculateTotalScore() {
  Score score = new Score(1, 2);
  return score.chinese + score.english;	
}

JVM通过逃逸分析,发现score对象并没有被方法外部引用,那么通过标量替换,会被编译为如下代码:

public int calculateTotalScore() {
  int chinese = 1;
  int english = 2;
  return chinese + english;	
}

TLAB

TLAB全名本地线程分配缓冲(Thread Local Allocation Buffer),栈上分配不了的对象,还是得分配到堆上,堆的特点是所有线程共享,那么大量线程同时在堆上申请创建空间就会发生同步争抢,效率低下。所以JVM默认给每一个线程在eden区中分配一个专有空间,那么对象创建时,在各自的空间中创建就不存在同步争抢的问题了。

这个空间不大,仅仅能存放小对象,大对象会直接分配到堆上。这是一种妥协设计,因为绝大多数的对象,都是小对象。

PLAB

除了Eden区,还有Survivor和老年代,它们也有各自的线程专有对象分配空间,叫PLAB(Promotion Local Allocation Buffers),即晋升本地分配缓存,PLAB和TLAB类似,都是为了避免多线程创建对象发生争抢而存在的,区别仅仅是作用区域不同。

总结

栈上分配是指线程创建私有对象时,在栈上创建而非堆,从而提升创建对象和清理对象的效率。而TLAB和PLAB是为了解决堆中对象创建时同步争抢问题。

欢迎访问原文地址来阅读最新版本
转载请注明出处:https://kang.fun/on-stack-allocation-and-tlab/
个人博客:kang.fun

Java中JVM在创建对象时,默认情况下是优先在堆上分配内存的。JVM内存模型中,堆是所有线程共享的一块内存区域,用于存放对象实例。然而,为了优化内存分配效率减少垃圾回收的压力,JVM引入了一些优化机制,其中之一是逃逸分析(Escape Analysis)[^1]。 逃逸分析是一种编译时的优化技术,JVM通过它来判断一个对象的作用域是否仅限于当前线程或某个方法内部。如果一个对象不会被外部访问,也就是说它不会“逃逸”出当前方法或线程,那么JVM可以对该对象进行优化,将其分配上而不是堆上。这种优化方式被称为分配(Stack Allocation)。分配的好处是内存可以随着帧的出而自动释放,不需要依赖垃圾回收机制,从而减轻了GC的压力。 此外,JVM还提供了另一种优化手段——标量替换(Scalar Replacement)。如果通过逃逸分析确定一个对象不会被外部访问,并且该对象可以被进一步分解为若干个基本类型的变量,那么JVM可以选择不创建该对象本身,而是直接将这些基本类型的变量分配帧或寄存器上。这种方式避免了因对象内存分配而导致的内存碎片问题,并且可以更高效地利用内存空间。标量替换在JDK 7之后是默认开启的[^3]。 尽管如此,分配标量替换并不是JVM的默认行为,而是基于运行时的优化策略。JVM会在类加载执行过程中动态决定是否应用这些优化措施。因此,在大多数情况下,对象仍然是在堆上分配内存的,只有在满足特定条件时,才会使用分配或标量替换进行优化[^1]。 ### 对象在堆上的分配过程 当JVM决定在堆上分配对象时,内存分配过程涉及多个步骤: 1. **类加载检查**:JVM首先检查类是否已经被加载,如果没有,则需要先加载类。 2. **内存分配策略**:根据堆的内存布局(是否规整),JVM选择合适的内存分配方式。如果堆内存是规整的,JVM可以通过简单的指针移动来分配内存;如果内存不规整,则需要维护一个空闲内存列表来查找合适的内存块[^4]。 3. **线程本地分配缓冲区(TLAB)**:为了提高多线程环境下对象分配的效率,JVM为每个线程分配一小块私有的内存区域(TLAB)。对象首先在TLAB分配,只有当TLAB空间不足时才需要从堆的共享内存中重新申请[^5]。 4. **初始化内存**:分配到的内存会被初始化为零值(不包括对象头静态变量),以确保对象的实例字段可以直接使用而无需显式初始化。 5. **设置对象头**:对象头中包含了一些元数据信息,如对象的哈希码、GC分代年龄、锁状态等,这些信息对于JVM管理回收对象非常重要。 综上所述,Java中JVM在创建对象时默认是在堆上分配内存的,但在某些特定条件下,JVM会通过逃逸分析标量替换等技术将对象分配上,以提高性能并减少GC的压力。 ```java // 示例代码:一个不会逃逸的对象 public class StackAllocationExample { public static void main(String[] args) { // 这个对象不会被外部访问,可能被JVM优化为分配 Point p = new Point(10, 20); System.out.println("Point: (" + p.x + ", " + p.y + ")"); } } class Point { int x; int y; public Point(int x, int y) { this.x = x; this.y = y; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值