searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Java逃逸分析浅析

2023-08-02 06:10:05
12
0

什么是逃逸分析

逃逸分析是一种决定变量存在于栈还是堆中的技术,在程序编译阶段,对代码中哪些变量需要在栈上分配,哪些变量需要在堆上分配的一种静态分析的方法 ,如下图所示:

其中栈是线程或协程独享,存在栈中的变量在函数执行结束则自动释放;而堆是由程序共享,在为变量分配空间时需要加锁,当变量生命周期结束之后依赖GC或手动回收 。

如果变量在堆上分配空间,不管是为变量分配空间阶段的加锁,还是回收阶段的GC回收或者手动回收,相对于栈上的无锁分配和自动释放,系统开销都比较大,程序性能也会受到影响,所以通过逃逸分析,能够优化程序性能,即能存储到栈的变量,尽量存储到栈。

Java语言中的逃逸分析

逃逸分析的概念在1999年即被提出,Java语言的逃逸分析技术在2006年12月随着jdk1.6的发布才开始出现。

在实现技术方面,Java的逃逸分析主要依赖JVM JIT即时编译器,在程序运行时识别未逃逸对象进行栈上分配。随着JIT技术的成熟,使得所有Java对象只能堆上分配变得不那么绝对。

Java语言的逃逸原则

1. 当一个对象在方法内部被定义后,对象只在方法内部使用,则认为没有发生逃逸。

2. 当一个对象在方法内部被定义后,它被方法外部所引用,则认为发生逃逸。

逃逸和未逃逸对象

返回StringBuffer对象,发生方法逃逸,例如被其他线程使用,则发生线程逃逸,此时在堆上分配;

不直接返回StringBuffer对象,返回不可变String,未发生逃逸,此时在栈上分配。

Java逃逸优化:Synchronized同步锁消除

在多线程编程中,通过Synchronized关键字进行线程同步本身比较耗费资源,JIT 编译器可以借助逃逸分析来进行优化,即如果确定一个对象只能被一个线程访问,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁。

具体可以通过-XX:+EliminateLocks(默认开启)可以开启同步消除,如下可以看到性能得到了提升:

1、逃逸优化前

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:-EliminateLocks  关闭锁消除( 1.8默认开启)

耗时:2377ms 

2、逃逸优化后

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启) 
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:+EliminateLocks 开启锁消除( 1.8默认开启)

耗时:12ms 

Java逃逸优化:标量替换

标量是仅能存储一个值的变量,比如Java代码中的int、double等原始数据类型。与之相反,聚合量则可能同时存储多个值,其中一个典型的例子便是Java对象。

标量替换这项优化技术,可以看成将原本对对象的字段的访问,替换为一个个原始数据类型变量的访问。

以上Point对象是一个聚合量,在alloc方法内部,对于x,y的赋值,分别使用创建Point对象的方式和直接设置int局部变量的方式,性能对比如下:

1、使用Point对象:耗时423ms

2、使用int变量:耗时20ms

Java逃逸优化:限制最大数组元素个数

在Java语言中,数组元素的内存分配一般是在堆内存上进行的。但随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。

具体是基于数组的元素个数和是否会被方法外部引用来确定是否可以在栈上分配,即如果栈可以容纳且不会被方法外部引用,则优先在栈上分配。

HotSpot JVM上的一个默认限制是大于64个元素的数组不会进行逃逸分析优化。这个大小可以通过启动参数-XX:EliminateAllocationArraySizeLimit=n来进行控制,n是数组的大小。

1、逃逸分析前

-XX:-DoEscapeAnalysis   关闭逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:EliminateAllocationArraySizeLimit=64 设置最大数组元素个数( 默认64)

耗时:1549ms

2、逃逸分析后

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:EliminateAllocationArraySizeLimit=64 设置最大数组元素个数( 默认64)

耗时:12ms

总结

1、对象不一定都在堆上分配内存

2、初始化数组时,切记分配下初始容量,并且尽量在-XX:EliminateAllocationArraySizeLimit=64 参数指定范围内

3、对象尽量不要逃出方法外使用 

 

0条评论
0 / 1000
javaxyz
3文章数
0粉丝数
javaxyz
3 文章 | 0 粉丝
javaxyz
3文章数
0粉丝数
javaxyz
3 文章 | 0 粉丝
原创

Java逃逸分析浅析

2023-08-02 06:10:05
12
0

什么是逃逸分析

逃逸分析是一种决定变量存在于栈还是堆中的技术,在程序编译阶段,对代码中哪些变量需要在栈上分配,哪些变量需要在堆上分配的一种静态分析的方法 ,如下图所示:

其中栈是线程或协程独享,存在栈中的变量在函数执行结束则自动释放;而堆是由程序共享,在为变量分配空间时需要加锁,当变量生命周期结束之后依赖GC或手动回收 。

如果变量在堆上分配空间,不管是为变量分配空间阶段的加锁,还是回收阶段的GC回收或者手动回收,相对于栈上的无锁分配和自动释放,系统开销都比较大,程序性能也会受到影响,所以通过逃逸分析,能够优化程序性能,即能存储到栈的变量,尽量存储到栈。

Java语言中的逃逸分析

逃逸分析的概念在1999年即被提出,Java语言的逃逸分析技术在2006年12月随着jdk1.6的发布才开始出现。

在实现技术方面,Java的逃逸分析主要依赖JVM JIT即时编译器,在程序运行时识别未逃逸对象进行栈上分配。随着JIT技术的成熟,使得所有Java对象只能堆上分配变得不那么绝对。

Java语言的逃逸原则

1. 当一个对象在方法内部被定义后,对象只在方法内部使用,则认为没有发生逃逸。

2. 当一个对象在方法内部被定义后,它被方法外部所引用,则认为发生逃逸。

逃逸和未逃逸对象

返回StringBuffer对象,发生方法逃逸,例如被其他线程使用,则发生线程逃逸,此时在堆上分配;

不直接返回StringBuffer对象,返回不可变String,未发生逃逸,此时在栈上分配。

Java逃逸优化:Synchronized同步锁消除

在多线程编程中,通过Synchronized关键字进行线程同步本身比较耗费资源,JIT 编译器可以借助逃逸分析来进行优化,即如果确定一个对象只能被一个线程访问,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁。

具体可以通过-XX:+EliminateLocks(默认开启)可以开启同步消除,如下可以看到性能得到了提升:

1、逃逸优化前

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:-EliminateLocks  关闭锁消除( 1.8默认开启)

耗时:2377ms 

2、逃逸优化后

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启) 
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:+EliminateLocks 开启锁消除( 1.8默认开启)

耗时:12ms 

Java逃逸优化:标量替换

标量是仅能存储一个值的变量,比如Java代码中的int、double等原始数据类型。与之相反,聚合量则可能同时存储多个值,其中一个典型的例子便是Java对象。

标量替换这项优化技术,可以看成将原本对对象的字段的访问,替换为一个个原始数据类型变量的访问。

以上Point对象是一个聚合量,在alloc方法内部,对于x,y的赋值,分别使用创建Point对象的方式和直接设置int局部变量的方式,性能对比如下:

1、使用Point对象:耗时423ms

2、使用int变量:耗时20ms

Java逃逸优化:限制最大数组元素个数

在Java语言中,数组元素的内存分配一般是在堆内存上进行的。但随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。

具体是基于数组的元素个数和是否会被方法外部引用来确定是否可以在栈上分配,即如果栈可以容纳且不会被方法外部引用,则优先在栈上分配。

HotSpot JVM上的一个默认限制是大于64个元素的数组不会进行逃逸分析优化。这个大小可以通过启动参数-XX:EliminateAllocationArraySizeLimit=n来进行控制,n是数组的大小。

1、逃逸分析前

-XX:-DoEscapeAnalysis   关闭逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:EliminateAllocationArraySizeLimit=64 设置最大数组元素个数( 默认64)

耗时:1549ms

2、逃逸分析后

-XX:+DoEscapeAnalysis   开启逃逸分析(1.8默认开启)
-XX:+PrintGC  开启GC日志打印(默认关闭)
-XX:EliminateAllocationArraySizeLimit=64 设置最大数组元素个数( 默认64)

耗时:12ms

总结

1、对象不一定都在堆上分配内存

2、初始化数组时,切记分配下初始容量,并且尽量在-XX:EliminateAllocationArraySizeLimit=64 参数指定范围内

3、对象尽量不要逃出方法外使用 

 

文章来自个人专栏
Java编程
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
1