JVM 逃逸分析

JVM 逃逸分析

1.1 背景

随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

在 Java 虚拟机中,对象是在 Java 堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

逃逸分析技术到现在还不是很成熟,虽然经过逃逸分析可以做标量替换、栈上分配、锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

1.2 何为逃逸分析

逃逸分析一种数据分析算法,基于此算法可以有效减少 Java 对象在堆内存中的分配。

Hotspot 虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象

分配到堆上。例如:

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

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

1.3 逃逸分析案例演示

1.3.1 逃逸对象

如下代码中的 StringBuffer 发生了逃逸,不会在栈上分配。

public StringBuffer append(String s1, String s2) {
 	StringBuffer sb = new StringBuffer();
 	sb.append(s1);
	sb.append(s2);
 	return sb;
}    

可以调整为如下写法,例如:

public String append(String s1, String s2) {
 	StringBuffer sb = new StringBuffer();
 	sb.append(s1);
 	sb.append(s2);
 	return sb.toString();
}

1.3.2 未逃逸对象

当一个对象在方法内创建,又没有被外界引用,此对象为非逃逸对象。例如:

public void create(int x,int y) {
 	Point p1= new Point(x,y);
	 //…
 	p1=null;
 
}

1.3.3 逃逸分析参数设置

在 JDK 1.7 版本之后,HotSpot 中默认就已经开启了逃逸分析,如果使用的是较早的

版本,开发人员则可以通过:

✓ 选项“-XX:+DoEscapeAnalysis"显式开启逃逸分析。

✓ 通过选项“-XX:+PrintEscapeAnalysis"查看逃逸分析的筛选结果。

建议:开发中能在方法内部应用对象的,就尽量控制在内部。

1.4 代码优化实践

1.4.1 概述

使用逃逸分析,编译器可以对代码做如下优化:

  • 栈上分配:将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引

    用不会发生逃逸,对象可能是栈上分配的候选。

  • 同步省略:又称之为同步锁消除,如果一个对象被发现只有一个线程被访问到,那么对

    于这个对象的操作可以不考虑同步。

  • 分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问

    到,那么对象的部分(或全部)可以不存储在内存,而是存储在 CPU 寄存器中。

1.4.2 栈上分配

/**
* 栈上分配测试(-XX:+DoEscapeAnalysis)
* -Xmx128m -Xms128m -XX:+DoEscapeAnalysis -XX:+PrintGC
*/
public class ObjectStackAllocationTests {
 	public static void main(String[] args) throws InterruptedException {
 		long start = System.currentTimeMillis();
 		for (int i = 0; i < 10000000; i++) {
 		alloc();
 	}
 		long end = System.currentTimeMillis();
 		System.out.println("花费的时间为: " + (end - start) + " ms");
        // 为了方便查看堆内存中对象个数,线程 sleep
 		TimeUnit.MINUTES.sleep(5);
 	}
	private static void alloc() {
 		byte[] data = new byte[10];//未发生逃逸
 	}
}

对如上代码运行测试时,分别开启和关闭逃逸分析,检查控制台日志的输出以及花费时

间上的不同。

1.4.3 同步锁消除

我们知道线程同步是靠牺牲性能来保证数据的正确性,这个过程的代价会非常高。程序

的并发行和性能都会降低。JVM 的 JIT 编译器可以借助逃逸分析来判断同步块所使用的锁

对象是否只能够被一个线程应用?假如是,那么 JIT 编译器在编译这个同步块的时候就会

取消对这部分代码上加的锁。这个取消同步的过程就叫同步省略,也叫锁消除。例如:

public class SynchronizedLockTest {
 	public void lock() {
 		Object obj= new Object();
 			synchronized(obj) {
 				System.out.println(obj);
 			}
 	}
}

1.4.4 标量替换分析

所谓的标量(scalar)一般指的是一个无法再分解成更小数据的数据。例如,Java 中

的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java

中的对象就是聚合量,因为他可以分解成其他聚合量和标量。在 JIT 阶段,如果经过逃逸分

析,发现一个对象不会被外界访问的话,那么经过 JIT 优化,就会把这个对象分解成若干个

变量来代替。这个过程就是标量替换。例如:

package com.java.jvm;
/**
* 标量替换测试 (-XX:+EliminateAllocations)
* -Xmx128m -Xms128m -XX:+DoEscapeAnalysis 
-XX:+PrintGC -XX:-EliminateAllocations
*/
public class ObjectScalarReplaceTests {
 	public static void main(String args[]) {
 		long start = System.currentTimeMillis();
 		for (int i = 0; i < 10000000; i++) {
 			alloc();
 		}
 		long end = System.currentTimeMillis();
 		System.out.println("花费的时间为: " + (end - start) + " ms");
 	}
 	private static void alloc() {
 		Point point = new Point(1,2);
 	}
 	static class Point {
 		private int x;
 		private int y;
 		public Point(int x,int y){
 			this.x=x;
 			this.y=y;
 		}
 	}
}

对于上面代码,假如开启了标量替换,那么 alloc 方法的内容就会变为如下形式:

private static void alloc() {
 	int x=10;
	int y=20;
 }

alloc 方法内部的 Point 对象是一个聚合量,这个聚合量经过逃逸分析后,发现他并

没有逃逸,就被替换成两个标量了。那么标量替换有什么好处呢?就是可以大大减少堆内存

的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。标量替换为栈上分配

提供了很好的基础。

1.5 小节面试分析

  • 什么是逃逸分析?

    ​ 逃逸分析一种数据分析算法,基于此算法可以有效减少 Java 对象在堆内存中的分配。

    Hotspot 虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象

    分配到堆上。

  • 逃逸分析有什么优势、劣势?

    优势:判断哪些对象是可以存储在栈内存中而不用存储在堆内存中的,从而让其随着线程的消逝而消逝,进而减少了 GC 发生的频率。

    ​ **劣势:**因为逃逸分析是需要消耗一定的性能去执行分析的,所以说如果方法中的对象全都是处于逃逸状态,那么就没有起到优化的作用,从而就白白损失了这部分的性能消耗。

  • 什么是栈上分配,为什么要栈上分配?

    ​ 将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引

    用不会发生逃逸,对象可能是栈上分配的候选。

  • 什么是锁消除,何时会触发锁消除?

    我们知道线程同步是靠牺牲性能来保证数据的正确性,这个过程的代价会非常高。程序

    的并发行和性能都会降低。JVM 的 JIT 编译器可以借助逃逸分析来判断同步块所使用的锁

    对象是否只能够被一个线程应用?假如是,那么 JIT 编译器在编译这个同步块的时候就会

    取消对这部分代码上加的锁。这个取消同步的过程就叫同步省略,也叫锁消除。

  • 什么是标量替换,标量替换可以解决什么问题?

    ​ 所谓的标量(scalar)一般指的是一个无法再分解成更小数据的数据。例如,Java 中

    的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java

    中的对象就是聚合量,因为他可以分解成其他聚合量和标量。在 JIT 阶段,如果经过逃逸分

    析,发现一个对象不会被外界访问的话,那么经过 JIT 优化,就会把这个对象分解成若干个

    变量来代替。这个过程就是标量替换。

    就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了

  • Hotspot 虚拟机中所有对象都是分配在堆中吗?

    是的

  • HotSpot 虚拟机中对象的栈上分配如何理解?

    本质上是标量替换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值