java之new一個對象是怎樣的過程?

  • 2019 年 10 月 30 日
  • 筆記

作為一名java碼農,在語言層面上,如何創建一個對象,想必大家的意識就是new關鍵字的使用了,在虛擬機中,對象的創建又是一個怎樣的過程呢?

虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入,解析和初始化過,若沒有,必須先執行相應的類載入過程。

類載入的過程在這篇文章中先不進行說明,簡單地說下,類載入的過程就是將我們的java源程式碼編譯後的class位元組碼文件載入進記憶體的過程,先說到這吧,後面會單獨寫一篇文章,大家一起交流交流。

在通過類載入檢查通過後,接下來虛擬機將為新生對象分配記憶體。對象所需記憶體大小在類載入完成後可確定,為對象分配空間的任務相當於把一塊確定大小的記憶體從Java堆中劃分出來,一般存在兩種方式,其一是指針碰撞,其二是空閑列表,具體選擇哪種分配方式是根據Java堆是否規整來決定的。

Java堆的規整同時又取決於所採用的垃圾收集器是否帶有壓縮整理的功能所決定的,我們都知道垃圾收集器存在標記-清除,標記-整理等,因此Java堆是否規整就看你使用的是什麼GC演算法了。為了確保記憶體分配時的執行緒安全,通常使用兩種解決方法:一種是對分配記憶體空間的動作進行同步處理–實際上虛擬機採用CAS配上失敗重試的方式保證更新操作的原子性;

另外一種是把記憶體分配的動作執行緒劃分在不同的空間之中,即每個執行緒在Java堆中預先分配一小塊記憶體,稱之為TLAB,是本地執行緒分配緩衝的簡寫形式,那個執行緒要分配記憶體,就在哪個執行緒的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定操作。

記憶體分配完成後,虛擬機需要將分配到的記憶體空間都初始化為零值,如果使用TLAB,這一工作可以提前至TLAB分配時進行。這一步操作保證了對象的實例欄位在java程式碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的數據類型所對應的零值。

接下來的動作就是虛擬機要對對象進行必要的設置了,一般一個對象是屬於某個類的實例中的一個,如何才能找到類的元數據資訊,對象的哈希碼就是hashCode了,對象的GC分代年齡等資訊,這些資訊是存在對象的對象頭之中,當上面的工作完成了之後,從虛擬機的角度來看,一個新對象已經產生,但是從Java程式的角度來看,對象的創建才剛剛開始,一般來說,執行new執行之後會接著執行<init>方法,把對象按照程式設計人員的思維進行初始化,這樣一個新對象才算完全產生出來。

在HotSpot虛擬機中,對象在記憶體中的存儲布局可以分為三塊區域:對象頭,實例數據和對齊填充。

HotSpot虛擬機的對象頭包括兩部分資訊,第一部分用於存儲對象自身的運行時數據,如GC分代年齡,哈希碼即hashCode,鎖狀態標識,執行緒持有的鎖,偏向執行緒ID等資訊,在這裡我採用的都是文字描述,關於對象頭資訊,有一張圖描述的很清楚,自行查閱吧,Mark Word被設計成一個非固定的數據結構,原因在於在極小的記憶體空間存儲盡量多的資訊,它會根據對象的狀態復用自己的存儲空間。

好了,我們繼續吧,第二部分是類型指針,並不是所有的虛擬機都有,由於我們在說hotSpot,類型指針即對象指向它的類元數據的指針,虛擬機通過這個指針來確定對象是哪個類的實例(句柄和直接指針),此外,如果對象是一個Java數組,那麼在對象頭中還必須有一塊用於記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據資訊確定java對象的大小,但是從數組的元數組中卻無法確定數組的大小,這塊內容稍顯晦澀難懂,大家有個印象就可以了,想深入了解的查閱對應的資料資訊吧。

實例數據部分才是存儲對象實例的有效資訊,也是在程式程式碼中定義的各種類型的欄位內容,無論是從父類繼承下來的,還是在子類中定義的,都需要記錄下來,這部分的存儲順序會受到虛擬機分配策略參數和欄位在Java源碼中定義的順序的影響。HotSpot虛擬機默認的分配策略是相同寬度的欄位總是被分配到一起,滿足這個前提條件下,在父類中定義的變數會出現在子類之前。

對齊填充部分不是必然存在的,它僅僅起著佔位符的作用,由於HotSpot虛擬機的自動記憶體管理機制要求對象的起始地址必須是8位元組的整倍數,因此,當對象的實例數據部分沒有對齊時,這個時候就需要對齊填充來補全了。

ok,這篇文章快要結束了,下面我們在說下一些內容,我們在程式中創建對象是為了使用對象,Java程式需要通過棧上的引用來操作堆上的具體對象,目前主流的訪問方式有使用句柄和直接指針兩種,如果使用句柄訪問的話,那麼Java堆中將會劃分一塊記憶體來作為句柄池,reference中存儲的就是對象的句柄池地址,而句柄中包含了對象實例數據與類型數據各自的具體地址資訊。如果使用直接指針訪問,那麼Java堆對象的布局中就必須考慮如何防止訪問類型數據的相關資訊了,而reference中存儲的直接就是對象地址。

兩種訪問對象的方式其實各自有自己的優勢,使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,使用直接指針訪問的方式優勢就是速度更快,因為它節省了一次指針定位所帶來的時間開銷