深入理解java虚拟机(十四)JVM逃逸分析
Posted 、Dong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解java虚拟机(十四)JVM逃逸分析相关的知识,希望对你有一定的参考价值。
前言
Java 中对象的创建一般会由堆内存去分配内存空间来进行存储,在堆内存空间不足的时候,GC 便会对堆内存进行垃圾回收,如果 GC 运行的次数过多,便会影响程序的性能,所以 “逃逸分析” 由此诞生,它的目的就是判断哪些对象是可以存储在栈内存中而不用存储在堆内存中的,从而让其随着线程的消逝而消逝,进而减少了 GC 发生的频率,这也是常见的 JVM 优化技巧之一。
一、什么是逃逸分析
“Java 中的对象是否都分配在堆内存中?”
——“不尽然”
逃逸分析(Escape Analysis)简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。
在方法中创建对象之后,如果这个对象除了在方法体中还在其它地方被引用了,此时如果方法执行完毕,由于该对象有被引用,所以 GC 有可能是无法立即回收的,此时便成为 内存逃逸现象。
逃逸 是一个动词,比如 A 从 B 中逃逸,那么此时这个 A 指的就是方法中创建的对象,B 指的就是这个方法体,即可以简单理解成这个对象逃逸出这个方法体。
二、逃逸状态
一个对象有三种逃逸状态:
-
全局逃逸(GlobalEscape):即一个对象的作用范围逃出了当前方法或者当前线程,
一般有以下几种场景:
① 对象是一个静态变量
② 对象是一个已经发生逃逸的对象
③ 对象作为当前方法的返回值 -
参数逃逸(ArgEscape):即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。
-
没有逃逸:即方法中的对象没有发生逃逸。
样例代码:
class A
public static B b;
public void globalVariablePointerEscape() // 给全局变量赋值,发生逃逸
b = new B();
public B methodPointerEscape() // 方法返回值,发生逃逸
return new B();
public void instancePassPointerEscape()
methodPointerEscape().printClassName(this); // 实例引用传递,发生逃逸
class B
public void printClassName(A a)
System.out.println(a.class.getName());
三、逃逸分析的优势
- 开启逃逸分析:-XX:+DoEscapeAnalysis
- 关闭逃逸分析:-XX:-DoEscapeAnalysis
- 显示分析结果:-XX:+PrintEscapeAnalysis
逃逸分析的作用,就是筛选出没有发生逃逸的对象,从而对它们进行以下三方面的优化
1.同步消除(锁消除)
因为同步锁是非常消耗性能的,所以当编译器确定一个对象没有发生逃逸时,它便会移除该对象的同步锁。
在 JDK1.8 中是默认开启的,但是要建立在已开启逃逸分析的基础之上。
- 开启锁消除:-XX:+EliminateLocks
- 关闭锁消除:-XX:-EliminateLocks
2.标量替换
首先要明白标量和聚合量,基础类型和对象的引用可以理解为标量,它们不能被进一步分解。而能被进一步分解的量就是聚合量,比如:对象。
对象是聚合量,它又可以被进一步分解成标量,将其成员变量分解为分散的变量,这就叫做标量替换。
这样,如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。
标量替换在 JDK1.8 中也是默认开启的,但是同样也要建立在已开启逃逸分析的基础之上。
- 开启标量替换:-XX:+EliminateAllocations
- 关闭标量替换:-XX:-EliminateAllocations
- 显示标量替换详情:-XX:+PrintEliminateAllocations
3.栈内存分配
栈内存分配很好理解,在上文中提过,就是将原本分配在堆内存上的对象转而分配在栈内存上,这样就可以减少堆内存的占用,从而减少 GC 的频次。
结尾
- 感谢大家的耐心阅读,如有建议请私信或评论留言。
- 如有收获,劳烦支持,关注、点赞、评论、收藏均可,博主会经常更新,与大家共同进步
以上是关于深入理解java虚拟机(十四)JVM逃逸分析的主要内容,如果未能解决你的问题,请参考以下文章