Java对象在堆上分配吗

昨天有个技术群里一个小伙伴分享了一次被虐的面试经历,其中一道题很有意思:Java中对象都会分配在堆上吗?

大部分小伙伴都在讨论类似:redis为什么快?怎么设计弹性伸缩的缓存系统?服务降级、服务熔断等等非常高大上的问题。但是在如此高大上的问题里,掺杂了这么看似简单的一道题,说明还是有点意思。

问了下小伙伴们,不为少数的小伙伴都会很简单的回答,对象分配在堆上啊,上学没认真听讲吗?

但是真的如此吗?先看代码:

import java.io.IOException;

/**
 * @author liuyan
 * @date 21:25 2020/4/2
 * @description
 */
public class Escape {
	private static int count = 1000000;

	public static void main(String[] args) throws InterruptedException, IOException {
		Escape escape = new Escape();
		for (int i = 0; i < count; i++) {
			escape.createDachou();
		}
		while (true) {
			Thread.sleep(10000);
		}
	}

	private void createDachou() {
		DaChou daChou = new DaChou("da", "chou");
	}

	static class DaChou {

		private String da;
		private String chou;

		public DaChou(String da, String chou) {
			this.da = da;
			this.chou = chou;
		}
	}
}

非常简单,我们创建1000000个DaChou对象,然后用JDK1.8运行,jmap看一下堆分布:

很明显,和预期不一样啊,怎么DaChou只有101440个对象呢。我的DaChou都哪里去了?

我们再运行一次,这一次我们加上一个参数:-XX:-DoEscapeAnalysis,jmap再看堆分布:

这次没问题了,和我们预期完全一致,堆上分配了1000000个DaChou对象。

-XX:-DoEscapeAnalysis这个参数到底做了什么,为什么对象没有在堆上分配?

这就要从Java虚拟机的即时编译说起,即时编译是指把字节码直接编译成处理器直接运行的指令程序。因为字节码,所以Java有一次编译到处运行的美誉,但是运行时又要转换为处理器对应的指令,所以又说Java慢。所以从很早就有了即时编译,但是即时编译又会很耗时,对极少数执行的代码进行即时编译又得不偿失。所以,HotSpot虚拟机只会对热点代码进行即时编译。在HotSpot中有C1、C2多个即时编译器,对于C1试用于需要启动较快的场景,对于C2试用于峰值性能高的场景。所以HotSpot采用了分层编译的模式,分为了五层:

  1. 解释执行;
  2. 执行不带 profiling 的 C1 代码;
  3. 执行仅带方法调用次数以及循环回边执行次数 profiling 的 C1 代码;
  4. 执行带所有 profiling 的 C1 代码;
  5. 执行 C2 代码。

这里不做展开,我们只需要知道虚拟机会对我们在运行中的代码进行数据收集,来判断是否需要即时编译来提高性能。在即时编译时,就有一种特别的优化,叫做逃逸分析。上面我们设置的参数-XX:-DoEscapeAnalysis也就是关闭了逃逸分析。

逃逸分析就表示在即使编译时,虚拟机会分析对象是否会逃逸,逃逸的依据有两个:1.对象是否存入堆中 2对象是否传入未知代码。

有了逃逸分析,虚拟机就可以做很多的事情,比如锁消除、标量替换。

锁消除很好理解,既然加锁的对象不会逃逸,也就是不会被其他线程看到,那对该对象加锁毫无意义,虚拟机会消除对于该对象的加锁、释放锁的操作。

对于标量替换,也就是把对于对象的访问,替换为该对象的局部变量的访问。相应的,如果该对象可以替换为多个标量表示,那么对于不会逃逸的对象,虚拟机会直接将其替换为多个标量,并且不会再堆中创建该对象,这些替换的标量会直接分配在栈上,随着方法的退出直接销毁了。这样加快了访问速度,同时也减少了堆空间的占用,减少gc压力。

对于标量替换可以使用参数-XX:+EliminateAllocations来设置,我们为了证实一下虚拟机确实进行了标量替换,我们打开逃逸分析、关闭标量替换(-XX:+DoEscapeAnalysis -XX:-EliminateAllocations),运行上边的程序,我们再看堆分布情况:

可以看到,即时开启了逃逸分析,但是在关闭标量替换的情况下,堆中依旧分配了1000000个DaChou对象。

所以,看似简单的一个题目,其实背后也有很深的内容可以挖掘。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章