虚拟机优化技术——逃逸分析

·  阅读 311
  • 逃逸分析 逃逸分析是目前JVM中比较前沿的优化技术,它不是直接的优化手段而是为其他优化手段提供依据的分析技术,逃逸分析的基本行为就是分析对象动态作用域。基本思想是,对于线程私有的对象,将它打散分配在栈上,而不分配在堆上。好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,可以提高性能。是栈上分配需要的技术基础。逃逸分析的目的是判断对象的作用域是否会逃逸出方法体。

  • 注意,任何可以在多个线程之间共享的对象,一定都属于逃逸对象(就像我们上面的静态变量u,它是分配在方法区中的,属于线程共享对象)。 如果对jvm中的内存分配,堆和栈的关系不了解的,可以看我的上一篇博客 www.jianshu.com/p/23695aaf1…

  • 栈上分配 我们知道jvm中几乎所有的对象都是在堆中分配的,那么为什么说几乎呢? 因为有可能是在栈上分配的。如果虚拟机开启逃逸分析的话。

  • 牵涉到的JVM参数: -XX:+DoEscapeAnalysis:启用逃逸分析(默认打开) -XX:-DoEscapeAnalysis:关闭逃逸分析(+号就是开启,-号就是关闭) -XX:+EliminateAllocations:标量替换(默认打开) -XX:+UseTLAB:本地线程分配缓冲(默认打开) -XX:+PrintGC:打出GC日志 现在我们来举个例子:

public class StackAlloc {

	public static class User{
		public int id = 0;
		public String name = "";
	}

	
	public static void alloc() {
		User u = new User();  //Object  在堆上分配的() ,有逃逸分析的技术 ,在栈中分配的
		u.id = 5;
		u.name = "King";
	}

	public static void main(String[] args) {
		long b = System.currentTimeMillis(); //开始时间
		for(int i=0;i<100000000;i++) {//一个方法运行1亿次()
			alloc();
		}
		long e = System.currentTimeMillis(); //结束时间
		System.out.println(e-b);//打印运行时间:毫秒
	}

}
复制代码

我们现在改变下虚拟机的配置参数

image.png
然后运行程序,查看打出日志

[GC (Allocation Failure)  65536K->584K(251392K), 0.0015989 secs]
[GC (Allocation Failure)  66120K->488K(251392K), 0.0006478 secs]
[GC (Allocation Failure)  66024K->480K(251392K), 0.0010588 secs]
[GC (Allocation Failure)  66016K->480K(316928K), 0.0015772 secs]
[GC (Allocation Failure)  131552K->512K(316928K), 0.0007369 secs]
[GC (Allocation Failure)  131584K->480K(438272K), 0.0005644 secs]
[GC (Allocation Failure)  262624K->448K(438272K), 0.0014815 secs]
[GC (Allocation Failure)  262592K->448K(700416K), 0.0003825 secs]
[GC (Allocation Failure)  524736K->448K(700416K), 0.0005757 secs]
[GC (Allocation Failure)  524736K->448K(1015296K), 0.0019906 secs]
706

Process finished with exit code 0

复制代码

###不启用逃逸分析的时候,我们可以看到,因为我们要在堆上分配100000000个u对象,所以打出很多GC回收的日志,垃圾回收,用时706秒 现在我们更改下配置,启用逃逸分析

image.png

12

Process finished with exit code 0
复制代码

###打开逃逸分析的时候,我们可以看到,才用时12秒,并且没有打出GC日志,说明没有垃圾回收,这就是jvm默认开启的逃逸分析优化技术,这时候u是在栈上分配的。我们前面说过,逃逸分析的基本行为就是分析对象动态作用域,因为这时候u对象只有在alloc方法里使用,并没有在alloc外使用,所以这时候jvm会开启逃逸分析。

现在我们在打开逃逸分析的同时,我们把方法改一下,不在alloc里创建u 对象,我们把u改成静态的变量,

public class StackAlloc {

	private static User u;

	public static class User{
		public int id = 0;
		public String name = "";
	}

	
	public static void alloc() {
		u = new User();  //Object  在堆上分配的() ,有逃逸分析的技术 ,在栈中分配的
		u.id = 5;
		u.name = "King";
	}

	public static void main(String[] args) {
		long b = System.currentTimeMillis(); //开始时间
		for(int i=0;i<100000000;i++) {//一个方法运行1亿次()
			alloc();
		}
		long e = System.currentTimeMillis(); //结束时间
		System.out.println(e-b);//打印运行时间:毫秒
	}



}

复制代码

我们看日志:

[GC (Allocation Failure)  65536K->536K(251392K), 0.0007084 secs]
[GC (Allocation Failure)  66072K->504K(251392K), 0.0011988 secs]
[GC (Allocation Failure)  66040K->456K(251392K), 0.0011393 secs]
[GC (Allocation Failure)  65992K->504K(316928K), 0.0015018 secs]
[GC (Allocation Failure)  131576K->504K(316928K), 0.0006266 secs]
[GC (Allocation Failure)  131576K->504K(438272K), 0.0011568 secs]
[GC (Allocation Failure)  262648K->477K(438272K), 0.0013559 secs]
[GC (Allocation Failure)  262621K->477K(700416K), 0.0002986 secs]
[GC (Allocation Failure)  524765K->477K(700416K), 0.0010896 secs]
[GC (Allocation Failure)  524765K->477K(1015296K), 0.0007153 secs]
771

Process finished with exit code 0
复制代码

#我们这一看出,就算打开了逃逸分析,但是u这时候并不符合逃逸分析的条件:User类型的对象u逃逸出方法alloc,所以这时候jvm是不开启逃逸分析的。

*总结: 我们知道热点跟踪技术,把热点代码,经常使用的代码变成本地代码,逃逸分析技术其实也是一个道理,把这个方法里经常使用的对象在栈上分配(按道理对象正常情况下是在堆上分配的),我们知道栈里的对象会跟着方法自行销毁,不需要进行垃圾回收,所以执行的速度很快,也没有GC日志。 如果对jvm中的内存分配,堆和栈的关系不了解的,可以看我的上一篇博客 www.jianshu.com/p/23695aaf1…

分类:
Android
标签: