Java逃逸分析之栈上分配内存

目录

  • 什么是逃逸分析?
  • 栈上分配内存

什么是逃逸分析?

在很早以前,Java代码从编写完毕到JVM执行至少需要两个过程:

  1. javac将Java代码编译成字节码class文件。
  2. JVM载入class文件后,由解释器来逐条将字节码指令解释翻译成本地机器码并执行。

因此,Java也被称为是一门”解释执行“的语言,由于解释执行比编译执行要慢,所以”Java程序很慢“在早期深入人心。
为了解决“解释执行”的效率问题,Java引入了JIT即时编译器,当JVM发现某段代码块运行的特别频繁时就会将这部分热点代码进行编译、优化并缓存,以便下次直接使用。

“逃逸分析技术”就是Java用来提升效率的技术手段之一,它通过动态的分析对象的作用域,来判断一个对象是否发生逃逸,如果没有逃逸,那么JVM可以做出以下优化:

  • 锁消除
    既然没有发生逃逸,位于线程私有的栈中天生线程安全,对数据的读写就无需加锁。
  • 标量替换
    如果可以,会对聚合量进行拆分,直接使用标量进行替换。(无法拆分,基本数据类型)
  • 栈上分配
    直接在方法栈中分配内存,方法出栈后就被销毁,减轻GC压力。

从JDK7开始,逃逸分析默认开启,也可通过参数-XX:[+|-]DoEscapeAnalysis自由设置。

栈上分配内存

“几乎”所有的对象都在堆中分配内存,方法栈中只保存基本数据类型以及对象引用。
不过随着逃逸分析技术的成熟,如果JVM发现对象没有发生逃逸,那么会改变这个内存分配策略,优先考虑在栈上分配内存,带来的好处是:方法出栈后内存就被销毁,无需GC额外回收。
GC回收时是需要暂停用户线程的,虽然现在的垃圾回收器已经将STW的的时间尽可能的减少了,但是不管怎么说,只要能减轻GC的压力就能进一步提高系统的吞吐量。

以上都是概念,下面通过几段示例代码实战一下开启逃逸分析后都带来了哪些优化。

public class EscapeAnalysis {

	static class MyClass {
		String name;
	}

	/**
	 * VM Args: -Xmx4G -Xms4G -XX:[-|+]DoEscapeAnalysis -XX:+PrintGCDetails
	 */
	public static void main(String[] args) throws Exception {
		long t1 = System.currentTimeMillis();
		for (int i = 0; i < 100000000; i++) {
			create();
		}
		long t2 = System.currentTimeMillis();
		System.out.println(t2 - t1);
	}

	static void create() {
		MyClass myClass = new MyClass();
		myClass.name = "";//没有逃逸
	}

	static MyClass createEscape() {
		MyClass myClass = new MyClass();
		myClass.name = "";
		return myClass;//发生逃逸,myClass可能会被其他线程访问
	}
}

性能上
构建1亿个对象实例,关闭逃逸分析耗时1018ms,开启逃逸分析后耗时8ms。

内存上
为了避免发生GC,只创建一千万个对象实例,打开GC日志,结果如下:
Java逃逸分析之栈上分配内存_第1张图片
Java逃逸分析之栈上分配内存_第2张图片
如果说以上数据还不能证明对象确实被分配在了栈上,那么只好祭出jmap大法了。
Java逃逸分析之栈上分配内存_第3张图片
Java逃逸分析之栈上分配内存_第4张图片

你可能感兴趣的:(Java)