最近在学习JVM,在看到这块地方,做一个简化的日志记录,部分内容抄录。

一般认为new出来的对象都是被分配在堆上,但是这个结论不是那么的绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中new出来的对象并一定分别在所认为的堆上。这两个点分别是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)。

栈上分配

在JVM中,堆是线程共享的,因此堆上的对象对于各个线程都是共享和可见的,只要持有对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但对于垃圾收集器来说,无论筛选可回收对象,还是回收和整理内存都需要耗费时间。

如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载。

JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。

栈上分配的技术基础:
一是逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。

二是标量替换:允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。

只能在server模式下才能启用逃逸分析,参数-XX:DoEscapeAnalysis启用逃逸分析,参数-XX:+EliminateAllocations开启标量替换(默认打开)。Java SE 6u23版本之后,HotSpot中默认就开启了逃逸分析,可以通过选项-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果。

逃逸分析

定义

逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。

Java在Java SE 6u23以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析,同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。

举个例子

只说定义有点抽象,举个小例子:

public StringBuilder escapeTest(String valOne, String vaTwo) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(valOne);stringBuilder.append(vaTwo);return stringBuilder;}public static void main(String[] args) {TlabDemo tlabDemo = new TlabDemo();StringBuilder ret1 = tlabDemo.escapeTest("a","b");System.out.println(ret1.toString());ret1.append("c");System.out.println(ret1.toString());}

stringBuilder是在方法的内部变量,而此时它被直接返回,这样stringBuilder就有可能被其他地方的方法或参数所改变,这样它的作用域就不只是escapeTest了,虽然它是一个局部变量,但其发生了“逃逸”,属于方法逃逸

逃逸类别

经过逃逸分析之后,可以得到三种对象的逃逸状态。
1、GlobalEscape(全局逃逸), 即一个对象的引用逃出了方法或者线程。例如,一个对象的引用是复制给了一个类变量,或者存储在在一个已经逃逸的对象当中,或者这个对象的引用作为方法的返回值返回给了调用方法。
2、ArgEscape(参数级逃逸),即在方法调用过程当中传递对象的应用给一个方法。这种状态可以通过分析被调方法的二进制代码确定。
3、NoEscape(没有逃逸),一个可以进行标量替换的对象。可以不将这种对象分配在传统的堆上。

另一种解释,其实都一样:
1、方法逃逸:在一个方法体内,定义一个局部变量,而它可能被外部方法引用,比如作为调用参数传递给方法,或作为对象直接返回。或者,可以理解成对象跳出了方法。

2、线程逃逸:这个对象被其他线程访问到,比如赋值给了实例变量,并被其他线程访问到了。对象逃出了当前线程。

再提一下“堆”

堆、栈是真实的内存物理区

java堆:是虚拟机中所管理的内存中区域最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。java堆是垃圾收集器管(GC)理的主要区域。

逃逸分析作用

逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。 逃逸分析(Escape Analysis)。

逃逸分析的好处

如果一个对象不会在方法体内,或线程内发生逃逸(或者说是通过逃逸分析后,使其未能发生逃逸)
1. 栈上分配
一般情况下,不会逃逸的对象所占空间比较大,如果能使用栈上的空间,那么大量的对象将随方法的结束而销毁,减轻了GC压力
2. 同步消除
如果你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。
3. 标量替换
Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们可以称为标量。相对的,如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。拆散后的变量便可以被单独分析与优化,可以各自分别在栈帧或寄存器上分配空间,原本的对象就无需整体分配空间了。

抄录:

同步消除:

在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。

如果同步块所使用的锁对象通过这种分析被证实只能够被一个线程访问,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这个取消同步的过程就叫同步省略,也叫锁消除。

public void f() {Object hollis = new Object();synchronized(hollis) {System.out.println(hollis);}
}

代码中对hollis这个对象进行加锁,但是hollis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉。优化成:

public void f() {Object hollis = new Object();System.out.println(hollis);
}

所以,在使用synchronized的时候,如果JIT经过逃逸分析之后发现并无线程安全问题的话,就会做锁消除。

标量替换

标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。

在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。

public static void main(String[] args) {alloc();
}private static void alloc() {Point point = new Point(1,2);System.out.println("point.x="+point.x+"; point.y="+point.y);
}
class Point{private int x;private int y;
}

以上代码中,point对象并没有逃逸出alloc方法,并且point对象是可以拆解成标量的。那么,JIT就会不会直接创建Point对象,而是直接使用两个标量int x ,int y来替代Point对象。

以上代码,经过标量替换后,就会变成:

private static void alloc() {int x = 1;int y = 2;System.out.println("point.x="+x+"; point.y="+y);
}

可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。

标量替换为栈上分配提供了很好的基础。

TLAB

TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域,在Eden区。
由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。

对象内存分配


我们这里不考虑栈上分配,我们这里考虑的是无法栈上分配需要共享的对象

对于 HotSpot JVM 实现,所有的 GC 算法的实现都是一种对于堆内存的管理,也就是都实现了一种堆的抽象,它们都实现了接口 CollectedHeap。当分配一个对象堆内存空间时,在 CollectedHeap 上首先都会检查是否启用了 TLAB,如果启用了,则会尝试 TLAB 分配;如果当前线程的 TLAB 大小足够,那么从线程当前的 TLAB 中分配;如果不够,但是当前 TLAB 剩余空间小于最大浪费空间限制(这是一个动态的值,我们后面会详细分析),则从堆上(一般是 Eden 区) 重新申请一个新的 TLAB 进行分配。否则,直接在 TLAB 外进行分配。TLAB 外的分配策略,不同的 GC 算法不同。例如G1:

1、如果是 Humongous 对象(对象在超过 Region 一半大小的时候),直接在 Humongous 区域分配(老年代的连续区域)。
2、根据 Mutator 状况在当前分配下标的 Region 内分配

再详细描述解析:
TLAB本身占用eEden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,
第一,废弃当前TLAB,这样就会浪费20KB空间;
第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste【最大浪费空间】的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。
这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
-XX:+PrintTLAB可以跟踪TLAB的使用情况。一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为

对象内存分配的两种方法

为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

指针碰撞(Serial、ParNew等带Compact过程的收集器)
假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。
空闲列表(CMS这种基于Mark-Sweep算法的收集器)
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。

TLAB解决的问题

TLAB 要解决的问题很明显,尽量避免从堆上直接分配内存从而避免频繁的锁争用

详细描述

看到一篇问题,对TLAB解析的非常详细:
csdn地址:https://blog.csdn.net/zhxdick/article/details/113625536
具体看这个。。。

JVM 栈分配、逃逸分析与TLAB相关推荐

  1. 小师妹学JVM之:逃逸分析和TLAB

    文章目录 简介 逃逸分析和栈上分配 TLAB简介 TLAB详解 设置TLAB空间的大小 TLAB中大对象的分配 TLAB空间中的浪费 总结 简介 逃逸分析我们在JDK14中JVM的性能优化一文中已经讲 ...

  2. JVM 栈分配与TLAB

    我们在学习使用Java的过程中,一般认为new出来的对象都是被分配在堆上,但是这个结论不是那么的绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中new出来的对象并一定分别在所 ...

  3. tlab java_Java中的逃逸分析和TLAB以及Java对象分配

    我们在学习使用Java的过程中,一般认为new出来的对象都是被分配在堆上,但是这个结论不是那么的绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中new出来的对象并一定分别在所 ...

  4. JVM 之 逃逸分析和TLAB

    来看一道面试题:所有的new 对象都是分配在堆上的吗?如果不是,是什么情况. 在没看到这道题目的时候,我对所有对象都分配在堆上的想法是没有一丝怀疑的,但是事实是不一定的. 逃逸分析 逃逸分析的定义 逃 ...

  5. 3.内存分配、逃逸分析与栈上分配、直接内存和运行时常量池、基本类型的包装类和常量池、TLAB、可达性分析算法(学习笔记)

    3.JVM内存分配 3.1.内存分配概述 3.2.内存分配–Eden区域 3.3.内存分配–大对象直接进老年代 3.3.1.背景 3.3.2.解析 3.4.内存分配–长期存活的对象进去老年代 3.5. ...

  6. 深入理解JVM逃逸分析

    本文来说下JVM中的逃逸分析 文章目录 JIT 逃逸分析 同步消除 标量替换 栈上分配 编译阈值 本文小结 JIT 即时编译(Just-in-time Compilation,JIT)是一种通过在运行 ...

  7. JVM---堆(逃逸分析与代码优化)

    堆-逃逸分析 堆是分配对象的唯一选择么? 在<深入理解Java虚拟机>中关于Java堆内存有这样一段描述: 随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配.标量替换优化技术将会导致 ...

  8. 逃逸分析和标量替换技术,你明白了吗

    文章目录 逃逸分析 怎么做逃逸分析后的优化? 什么是线程分配缓冲TLAB? 什么是标量替换? 实战 开始逃逸分析(默认开启的) 关闭逃逸分析-XX:-DoEscapeAnalysis 逃逸分析 逃逸分 ...

  9. 面试官:请你详细说说Go的逃逸分析【文末送福利】

    ‍ ‍ 这是我在8月份整理分享的一系列后端工程师求职面试相关的文章,知识脉络图如下: JAVA/GO/PHP 面试常问的知识点 DB:MySql PgSql Cache: Redis MemCache ...

最新文章

  1. 脑出血遇到深度学习,是否可以无所遁形?
  2. Arista将Spine-Leaf路由/交换产品进行芯片升级
  3. 深度研究 | 区块链在征信业的应用探讨:切中了痛点,但也面临四大挑战
  4. 你做过的项目会逐渐形成你自己的认知和别人对于你的看法~剑桥工程硕士陶瓷有感
  5. u盘安装linux 提示no such device_Linux 网络基础设计
  6. 【渝粤题库】陕西师范大学210001儿童心理学 作业(高起专、高起本)
  7. Compiler__visual_studio_2010_pro 激活码
  8. 微型计算机是一种将CPU,Bwnhlq计算机一级考试选择题题库之微型计算机基础题及答案(XX年最新版)1.doc...
  9. 关于mpvue 切换页面数据没清空
  10. java实现数据的Excel导出(合并单元格、样式等)
  11. Visio图片导入到Word里面的步骤
  12. 小米浏览器禁用java_小米手机调试总出现“INSTALL_CANCELED_BY_USER”
  13. 计算机识别人脸原理,人脸识别:原理、方法与技术
  14. Stressful Training(二分+贪心+优先队列)
  15. iOS APP适配iPhone X全屏
  16. Celery在Django下安装配置
  17. Web3 是什么?为何应该关注?
  18. 5G工作原理详解(解释图解)
  19. 苏州 计算机 在职研究生,苏州地区,可以报考哪些在职研究生院校!
  20. 读《我能记住 - 读写记忆困难儿童实战》

热门文章

  1. HTTTP的post和get的区别
  2. vulkan官方教程流程图
  3. ssr手机_钢铁之心——DC评水月雨SSR“超级银船”
  4. C语言 判断偶数个数和奇数个数
  5. 物理网络设计——结构化综合布线系统(6个子系统)学习心得
  6. 中国科协副主席、书记处书记孟庆海造访华扬联众并举办座谈会
  7. [iOS]App Store Connect添加银行卡时的CNAPS代码查询
  8. Javascript-style属性
  9. XTU OJ 1281 Cute String
  10. 社交礼仪--个人礼仪