首页 >> 大全

JVM中TLAB(Thread Local Allocation Buffer)

2023-09-21 大全 24 作者:考证青年

1、为什么有TLAB( Local )

总结一句话:堆上存放的对象实例多线程不安全,并且影响分配速度。如果能将对象实例分配在独立的一块区域,那就能解决多线程不安全和分配速度慢问题,这就是TLAB。

2、什么是TLAB

3、TLAB使用

4、通过逃逸分析判断对象实例可能被优化成栈上分配

如何将堆上的对象分配到栈,需要使用逃逸分析手段。

在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:

随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析( )后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配(TLAB).。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

此外,基于深度定制的,其中创新的GCIH(GC heap)技术实现off-heap,将生命周期较长的Java对象从heap中移至heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的。

5、逃逸分析概述

这是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

通过逃逸分析,Java 编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。

逃逸分析的基本行为就是分析对象动态作用域:

发生逃逸和未发生逃逸实例

public class EscapeAnalysis {public EscapeAnalysis obj;/*** 方法返回EscapeAnalysis对象,发生逃逸* @return*/public EscapeAnalysis getInstance() {return obj == null ? new EscapeAnalysis() : obj;}/*** 为成员属性赋值,发生逃逸*/public void setObj() {this.obj = new EscapeAnalysis();}/*** 对象的作用于仅在当前方法中有效,没有发生逃逸*/public void useEscapeAnalysis() {EscapeAnalysis e = new EscapeAnalysis();}/*** 引用成员变量的值,发生逃逸*/public void useEscapeAnalysis2() {EscapeAnalysis e = getInstance();}
}

没有发生逃逸的对象,则可以分配到栈上,随着方法执行的结束,栈空间就被移除(这种主动释放内存的方式相比堆内对象等待GC要更好)。

6、逃逸分析设置

参数设置:

在JDK 6u23 版本之后,中默认就已经开启了逃逸分析

这个默认大前提是启用了-模式,当然JVM默认也是-模式

如果使用的是较早的版本,开发人员则可以通过:

怎么判断是否启用了逃逸分析呢?

如果结果是“-XX:-”则为未启用,如果是“-XX:+”则启用了

7、代码实例比对启用逃逸分析和非逃逸分析

下述代码在主函数中进行了1亿次alloc。调用进行对象创建,User实例的创建作用域未出方法,如果启用逃逸分析,则会启用TLAB。咱们看下启用和不启用逃逸分析,执行时间和堆内对象使用情况。

/*** @author liuchao* @date 2023/2/24*/
public class Test {public static void main(String[] args) {long start = System.currentTimeMillis();for (int i = 0; i < 10000000; i++) {alloc();}// 查看执行时间long end = System.currentTimeMillis();System.out.println("花费时间:" + (end - start));// 方便查看堆内存中对象个数,线程sleeptry {Thread.sleep(100000);} catch (InterruptedException e) {throw new RuntimeException(e);}}public static void alloc() {//未发生逃逸User user = new User();}}class User {private int age;private String userName;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}
}

7.1、启用逃逸分析

默认就启用了,不需要配置,直接看执行结果。

7.2、不启用逃逸分析

配置vm选项:-XX:-

查看执行结果

7.3、结论

相同的代码,启用逃逸分析进行1亿次alloc用时7ms,堆上User对象实例占比37%;

不启用逃逸分析进行1亿次alloc用时70ms,堆上User对象实例占比93%;

可以看出相差巨大,所以啊开发中能使用局部变量的,就不要使用在方法外定义。

8、逃逸分析的缺点

关于逃逸分析的论文在1999年就已经发表了,但直到JDK1.6才有实现,而且这项技术到如今也并不是十分成熟。

其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的。那这个逃逸分析的过程就白白浪费掉了。

虽然这项技术并不十分成熟,但是它也是即时编译器优化技术中一个十分重要的手段。

注意到有一些观点,认为通过逃逸分析,JVM会在栈上分配那些不会逃逸的对象,这在理论上是可行的,但是取决于JVM设计者的选择。据我所知, JVM中并未这么做,这一点在逃逸分析相关的文档里已经说明,所以可以明确所有的对象实例都是创建在堆上。

目前很多书籍还是基于JDK7以前的版本,JDK已经发生了很大变化,字符串的缓存和静态变量曾经都被分配在永久代上,而永久代已经被元数据区取代。但是,字符串缓存和静态变量并不是被转移到元数据区,而是直接在堆上分配,所以这一点同样符合前面一点的结论:对象实例都是分配在堆上。

tags: jvm

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了