import com.google.common.base.Stopwatch;import java.util.concurrent.TimeUnit;public class StackTest { public static void main (String[] args) { Stopwatch started = new Stopwatch(); started.start(); User user = null ; for (long i = 0 ; i 1000_000_000; i++) { user = new User(); } started.stop(); System.out.println(started.elapsed(TimeUnit.MILLISECONDS) + "ms" ); //不加打印 300ms //加了打印 3000ms // System.out.println(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; } }
上面的一个简单的代码是测试 Java 创建对象的性能,如果没有
System.out.println(user);
输出的时间是 300ms左右,如果加上性能是 3000ms 左右,整整慢了 10 倍左右。(具体需要时间根据电脑的配置决定)。
看似很简单的代码,却会带来这样的性能消耗,确实很让人费解。为了弄清楚这个问题,我们需要讨论下,java 代码分配的规则。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
在前面的博客已经提过 Java 对象的分配过程,具体流程图如下:
栈上分配是 Java 虚拟机提供的一项优化技术,将线程私有的对象打散分配在栈上,栈上分配的对象回收直接 POP 出站,不需要垃圾回收器的介入,效率很高。当然栈上分配也需要一些特殊的条件:
对象不能出现逃逸(JVM 参数:
-XX:+DoEscapeAnalysis
)
对象可以进行标量替换,即是使用字段来表示对象(
-XX:+EliminateAllocations
)。
如 demo 所示,我们可以是用 age 和 username 两个字段来代替 User 对象。
TLAB Thread Local Allocation Buffer, 即:线程本地分配缓存。这是一块线程专用的内存分配区域。TLAB 占用的是 eden 区的空间。在TLAB 启用的情况下(默认开启),JVM会为每一个线程分配一块TLAB区域。
使用 TLAB 是为了加速对象的分配。由于对象一般分配在堆上,而堆是线程共用的,因此可能会有多个线程在堆上申请空间,而每一次的对象分配都必须线程同步,会使分配的效率下降。
考虑到对象分配几乎是 Java中 最常用的操作,因此 JVM 使用了 TLAB 这样的线程专有区域来避免多线程冲突,提高对象分配的效率。
同样,TLAB 空间一般不会太大(占用 eden 区),所以大对象无法进行 TLAB 分配,只能直接分配到堆上。
分配策略:
一个100KB的TLAB区域,如果已经使用了80KB,当需要分配一个30KB的对象时,TLAB是如何分配的呢?可以有两种情况:
将这个 30KB 的对象直接分配到堆上,保留当前 TLAB(当有小于 20KB 的对象请求 TLAB 分配时可以直接使用该 TLAB 区域)。
JVM选择的策略是:在虚拟机内部维护一个叫
refill_waste
的值,当请求对象大于
refill_waste
时,会选择在堆中分配,反之,则会废弃当前 TLAB,新建 TLAB来分配新对象。【默认情况下,TLAB和
refill_waste
都是会在运行时不断调整的,使系统的运行状态达到最优。】
JVM参数解析
参数
作用
备注
-XX:+UseTLAB
启用TLAB
默认启用
-XX:TLABRefillWasteFraction
设置允许空间浪费的比例
默认值:64,即:使用1/64的TLAB空间大小作为refill_waste值
-XX:-ResizeTLAB
禁止系统自动调整TLAB大小