三、逃逸分析
1. 堆是对象存储的唯一选择吗?
1 . 随着JIT编译的发展 和 逃逸分析技术 逐渐成熟,堆不是对象存储的唯一选择
2 . 如果经过逃逸分析( Escape Analysis) 后发现,一个对象并没有逃逸出方法的话
- 会被优化成栈上分配(堆外存储技术)
- 无需在堆上分配内存
- 无需进行垃圾回收
- 无需分配对象时进行加锁
3 . 比如TaoBaoVM: 将生命周期较长的java对象从堆中移到堆外
GC不负责管理堆外的java对象,从而降低GC的回收频率和提升GC的回收效率
2. 逃逸分析
1 . 如何将堆中的对象分配到栈? 需要使用逃逸分析
- 逃逸分析,Java HotSpot 编译器能够分析一个对象引用使用范围,决定是否要将对象分配在堆上
1.1 如果一个对象在方法中被定义后,对象只在方法内部使用,则没有发生逃逸分析
1.2 一个对象在方法中被定义后,被外部方法引用,则认为发生逃逸分析( 比如当作参数传递出去)
2 . 如果没有发生逃逸的对象,则可以分配在栈上
2.1 随着方法的执行,栈空间就被移除( 弹栈)
2.2 无需进行垃圾回收
3. 分析例子
JDK7后,默认开启逃逸分析 -XX:-DoEscapeAnalysis, 关闭逃逸分析检测
3.1 案例一
package com. nike. erick. d06 ;
import java. util. ArrayList ;
import java. util. List ;
public class Demo07 {
public static StringBuffer method01 ( String name, String address) {
StringBuffer sb = new StringBuffer ( ) ;
sb. append ( name) ;
sb. append ( address) ;
return sb;
}
public static String method02 ( String name, String address) {
StringBuffer sb = new StringBuffer ( ) ;
sb. append ( name) ;
sb. append ( address) ;
return sb. toString ( ) ;
}
public static void method03 ( ) {
List < String > list = new ArrayList < > ( ) ;
list. add ( "erick" ) ;
return ;
}
}
3.2 案例分析
package com. nike. erick. d06 ;
public class ErickAnalysis {
public ErickAnalysis erickAnalysis;
public ErickAnalysis getInstance ( ) {
return erickAnalysis == null ? new ErickAnalysis ( ) : erickAnalysis;
}
public void setErickAnalysis ( ) {
this . erickAnalysis = new ErickAnalysis ( ) ;
}
public void create ( ) {
ErickAnalysis erickAnalysis = new ErickAnalysis ( ) ;
}
public void createMethod ( ) {
ErickAnalysis erickAnalysis = getInstance ( ) ;
}
}
Best Practise : 开发中能使用局部变量的,就不要在方法外面定义
4. 代码优化
4.1 栈上存储
package com. nike. erick. d06 ;
import java. util. concurrent. TimeUnit ;
public class Demo08 {
public static void main ( String [ ] args) throws InterruptedException {
long startTime = System . currentTimeMillis ( ) ;
for ( int i = 0 ; i < 10000000 ; i++ ) {
createStudent ( ) ;
}
System . out. println ( "cost time:" + ( System . currentTimeMillis ( ) - startTime) ) ;
TimeUnit . SECONDS . sleep ( 100 ) ;
}
public static void createStudent ( ) {
Student student = new Student ( ) ;
}
}
class Student {
}
4.2 同步省略/ 锁消除
- 锁消除: 线程在执行临界区代码的时候,如果发现没有其他线程和本线程争夺锁资源
JIT 编译器就会将该锁优化掉,从而提升执行效率
- 编译后的字节码,包含了锁的字节码,但是在实际运行的时候会去掉
package com. nike. erick. d06 ;
import java. util. ArrayList ;
import java. util. List ;
public class Demo09 {
public static void main ( String [ ] args) {
}
public static void method01 ( ) {
List < String > list = new ArrayList < > ( ) ;
synchronized ( list) {
System . out. println ( "Hello" ) ;
}
}
}
4.3 标量替换
- 有的对象可能不需要作为一个连续的内存结构存储也可以被访问到
- 对象的部分或全部可以不存储在堆上,而是存储在栈上
1 . 标量:无法再分解为更小的数据的数据, 比如java中原始类型
2 . 聚合量:可以分解的数据,比如java中的对象,可以分解为其他聚合量和标量
JIT阶段:如果发现一个对象不会被外界访问,经过JIT优化,就会把这个对象拆解为
若干个包含其中《成员变量》来代替,就是标量替换
- 减少堆内存占用
- 减少垃圾回收
-XX:+EliminateAllocations 开启标量替换,默认打开,允许将对象打散粉配在栈上
- 变量替换是基于逃逸分析的基础上的,开启了逃逸分析后,标量替换才能发生