繼上一篇 JVM學習之路1-內存模型 介紹完JVM內存模型以後,這篇準備聊聊對象的內存佈局以及逃逸分析。咱們知道對象通常是分配在堆上的,可是你知道對象在堆上是怎麼存放的嗎?咱們平時程序中在使用的時候是怎麼找到對象的?
知識點
一、內存對象佈局
二、逃逸分析html
先說一下咱們平時是怎麼建立對象的A a = new A();
如上所示,一個對象A就被建立出來了。看似簡單的一行語句,其實虛擬機爲咱們作了不少事情。
首先虛擬機去常量池中查找是否有類A的符號引用,並檢查該符號引用的類A是否已經被虛擬機所加載過,若是沒有則先進行加載(具體的類加載機制咱們會在後面的系列文章中介紹),若是已經被加載過則能夠肯定爲該類對象所分配的內存大小並進行內存分配,大概流程以下:
這裏涉及到兩種分配方式:指針碰撞和空閒列表java
簡單來講,基於內存規整的前提下將內存分爲兩部分,用過的內存放到左側,沒用過的內存放到右側,中間放一個指針來分界,當爲對象分配內存時,向空閒區移動一塊該對象大小的空間:算法
上面是基於內存規整的狀況下進行指針碰撞,若是內存不規整的話,怎麼處理?那就要說到空閒列表了,簡單來講就是已分配內存和未分配內存是交錯的,虛擬機維護一份列表來知道哪些內存是可用的,在進行對象分配的時候會從列表中取一塊足夠空間的內存,分配結束後更新列表:
上面兩種分配方式哪一種更好呢?這個沒有必定哪一個好或者哪一個很差,取決於java的內存是否規整,而java內存是否規整又取決於所用的垃圾收集器是否帶有壓縮整理法決定,在後面介紹垃圾收集器的時候再具體聊。segmentfault
分配對象的對象的時候不是線程安全的,可能在分配對象A的時候,指針都沒來得及修改對象B又拿到原來的指針進行分配,如何避免呢?虛擬機在這方面有兩種解決方案:
一、分配對象更新指針的時候使用CAS來保證操做的原子性
二、使用TLAB機制,也就是對於每一個線程都先分配一個緩存進行內存的預分配,每次線程須要分配申請內存的時候都先在該緩存中進行,只有緩存不夠的時候再加鎖分配新的TLAB(-XX:+/-UseTLAB)。緩存
這裏我畫了個圖,看這個圖基本上你們就比較清晰了,我仍是大概解釋一下。安全
存放兩部分數據:
一、運行時數據,好比哈希碼、gc分代年齡、鎖狀態等。
二、類型指針,用來查找該對應指向的類元數據在哪,經過該類元數據就知道對象屬於哪一個類的對象。jvm
就是存放對象的字段內容,舉個栗子:
存的就是age和name信息。函數
沒啥意義,起到佔位符的做用,由於虛擬機分配對象大小是8字節的倍數,爲了可以補齊。佈局
對象內存分配和對象結構都講完了,那咱們在方法中是怎麼訪問到對象的呢?上一篇文章中(jvm學習之路1-內存模型),咱們在介紹jvm內存模型的時候說到了虛擬機棧,在方法調用的時候會產生一個棧幀,棧幀中存放了局部標量信息,就是這個局部變量表裏對於對象會有一個reference,該內容存的就是對象的引用,咱們就是經過它來找到對象數據的。而引用又分爲兩種:
一、對象句柄池,優勢是穩,對象地址變了,不影響棧中引用;
二、對象指針,優勢是快,數據直接找到,少一步中間商(hotspot方式);
這兩個概念比較簡單,咱們參照書裏說的,直接給兩幅圖:性能
對象內存佈局基本講完了,回顧一下,咱們上面講的對象都是在堆上分配的,那是否是全部對象都是在堆上分配的呢?答案固然是否認的,這裏就是咱們這部分想要說的逃逸分析技術,他其實就是jvm的一種優化。
正常對象在堆上分配,由gc銷燬,可是這樣是比較耗性能的,你們都知道對於一個程序來講,gc次數越少會好,爲了儘可能減小gc消耗,逃逸分析技術就誕生了。
網上有位博友這麼形容逃逸,用了一段簡單直接的代碼,我以爲挺直截了當的,能夠供參考:
stringBuilder是在方法的內部變量,而此時它被直接返回,這樣stringBuilder就有可能被其餘地方的方法或參數所改變,這樣它的做用域就不僅是demo1了,雖然它是一個局部變量,但其發生了「逃逸」。
那麼,我能夠改一下代碼:
如此,就沒有返回StringBuilder,而是toString(),那麼StringBuilder沒有從方法中直接脫離,就沒有發生逃逸。因此咱們寫代碼的時候也要注意利用好這點優化。
逃逸分析,是一種能夠有效減小Java 程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法。經過逃逸分析,Java Hotspot編譯器可以分析出一個新的對象的引用的使用範圍從而決定是否要將這個對象分配到堆上。
通常狀況下,不會逃逸的對象所佔空間比較大,若是能使用棧上的空間,那麼大量的對象將隨方法的結束而銷燬,減輕了GC壓力
若是你定義的類的方法上有同步鎖,但在運行時,卻只有一個線程在訪問,此時逃逸分析後的機器碼,會去掉同步鎖運行。
Java虛擬機中的原始數據類型(int,long等數值類型以及reference類型等)都不能再進一步分解,它們能夠稱爲標量。相對的,若是一個數據能夠繼續分解,那它稱爲聚合量,Java中最典型的聚合量是對象。若是逃逸分析證實一個對象不會被外部訪問,而且這個對象是可分解的,那程序真正執行的時候將可能不建立這個對象,而改成直接建立它的若干個被這個方法使用到的成員變量來代替。拆散後的變量即可以被單獨分析與優化,能夠各自分別在棧幀或寄存器上分配空間,本來的對象就無需總體分配空間了。
jdk1.8以上是默認開啓的。
開啓:-XX:+DoEscapeAnalysis
關閉:-XX:-DoEscapeAnalysis
這篇文章主要介紹了兩部份內容:
一、jvm中對象的內存佈局,包括內存分配、對象結構組成、如何訪問對象。
二、jvm的優化技術-逃逸分析,什麼是逃逸分析、逃逸分析的好處、如何使用逃逸分析。
參考資料:
周志明《深刻理解java虛擬機》
http://www.javashuo.com/article/p-tktsyeei-gv.html