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對象。

所以,看似簡單的一個題目,其實背後也有很深的內容可以挖掘。

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