本文主要是介绍Java对象一定分配在堆上吗?5min读懂逃逸分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. 引入
首先回答标题中的问题:Java对象一定会被分配到堆上吗?答案是:不一定。
Java
中创建的对象一般会分配到堆上,当堆空间不足时,就会触发GC
进行垃圾回收,但是GC
次数太多会影响程序的性能。
在编译期间,编译器会对代码做很多优化,为了减少内存堆分配压力,JVM
提供了一项重要优化技术:逃逸分析。逃逸分析得出的结论为后续优化措施提供依据。
2. 什么是逃逸分析
逃逸分析(Escape Analysis
):JVM
提供的一种优化技术,用于分析对象会不会发生逃逸。
Q:如何理解逃逸?
逃逸可以理解为会不会在作用域范围外被调用。如:一个方法内定义的变量,会不会在这个方法外被使用,如果否,则认为未逃逸;如果是:则认为会发生逃逸,这就是方法逃逸。
根据上述的理解,可以分为不同的逃逸方式。对象的逃逸程度从高到低:
- 线程逃逸:一个对象在方法内被定义后,可能被外部线程访问,如:赋值给可以在其他线程中访问的实例变量;
- 方法逃逸:一个对象在方法内被定义后,可能会被外部方法引用;
- 不逃逸:仅在作用域范围内使用。
根据逃逸分析的结果来决定优化策略
3. 优化策略
3.1 栈上分配(Stack Allocations
)
-
将对象分配到栈上,对象占用的内存空间可以随着栈帧出栈(即方法的结束)而销毁,这样垃圾收集的压力会下降很多。
-
整个过程通过判断对象,来决定其是否必须要存在堆上,如果不需要的话,则可以被分配到栈上,栈随着线程的消逝而消逝,这样能够减少了
GC
的频率,从而提高性能。 -
栈上分配支持方法逃逸,不支持线程逃逸。
【例如】
Java
复制代码
public void test(){ Student s = new Student(); s.setName("张三"); s.setAge(22); System.out.println(s.getAge()); }
逃逸分析后得出的结论为:不逃逸,对象
s
只作用于该方法内,不会被其他方法/线程引用,所以该对象可以分配到栈上。
Java
复制代码
public Student test(){ Student s = new Student(); s.setName("张三"); s.setAge(22); return s; }
该方法的返回值为Student对象,逃逸分析后,得出的结论是:对象s可能会被其他方法/线程引用,所以该对象只能分配到堆上。
3.2 标量替换(Scalar Replacement
)
Q:什么是标量?
标量可以理解为:不可拆解的数据,如:
int
,long
等数值类型。与之相对的概念为聚合量(
Aggregate
):即可以拆解的数据,如:Java中的对象。
标量替换就是将Java
对象拆散,根据程序访问的情况,将其用到的成员变量恢复到原始类型来访问。
这样做的好处:对象的成员变量在栈上分配和读写;为后续进一步优化创造条件。可以将标量替换看作栈上分配的一种特例,实现更加简单,但对逃逸的要求更高,不允许对象逃逸出方法范围内。
【例如】
Java
复制代码
//标量替换前的代码 public static void main(String[] args) { User user = new User("张三",33); System.out.println("姓名:" + s.getName() + " 年龄:" + s.getAge()); } // 标量替换后的代码 public static void main(String[] args) { String name = "张三"; int age = 33; System.out.println("姓名:" + name + " 年龄:" + age); }
3.3 同步消除(Synchronization Elimination
)
线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以安全消除掉。
如果JVM
通过逃逸分析,发现一个对象只能从一个线程访问到,访问这个对象时可以不加同步锁,如:如果程序中使用了synchronized
锁,JVM
会将synchronized
锁消除。
4. Java对象内存分配流程
这篇关于Java对象一定分配在堆上吗?5min读懂逃逸分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!