java之new一個對象是怎樣的過程?
- 2019 年 10 月 30 日
- 筆記
作為一名java碼農,在語言層面上,如何創建一個對象,想必大家的意識就是new關鍵字的使用了,在虛擬機中,對象的創建又是一個怎樣的過程呢?
虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載,解析和初始化過,若沒有,必須先執行相應的類加載過程。
類加載的過程在這篇文章中先不進行說明,簡單地說下,類加載的過程就是將我們的java源代碼編譯後的class位元組碼文件加載進內存的過程,先說到這吧,後面會單獨寫一篇文章,大家一起交流交流。
Java堆的規整同時又取決於所採用的垃圾收集器是否帶有壓縮整理的功能所決定的,我們都知道垃圾收集器存在標記-清除,標記-整理等,因此Java堆是否規整就看你使用的是什麼GC算法了。為了確保內存分配時的線程安全,通常使用兩種解決方法:一種是對分配內存空間的動作進行同步處理–實際上虛擬機採用CAS配上失敗重試的方式保證更新操作的原子性;
另外一種是把內存分配的動作線程劃分在不同的空間之中,即每個線程在Java堆中預先分配一小塊內存,稱之為TLAB,是本地線程分配緩衝的簡寫形式,那個線程要分配內存,就在哪個線程的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定操作。
接下來的動作就是虛擬機要對對象進行必要的設置了,一般一個對象是屬於某個類的實例中的一個,如何才能找到類的元數據信息,對象的哈希碼就是hashCode了,對象的GC分代年齡等信息,這些信息是存在對象的對象頭之中,當上面的工作完成了之後,從虛擬機的角度來看,一個新對象已經產生,但是從Java程序的角度來看,對象的創建才剛剛開始,一般來說,執行new執行之後會接着執行<init>方法,把對象按照程序設計人員的思維進行初始化,這樣一個新對象才算完全產生出來。
在HotSpot虛擬機中,對象在內存中的存儲布局可以分為三塊區域:對象頭,實例數據和對齊填充。
HotSpot虛擬機的對象頭包括兩部分信息,第一部分用於存儲對象自身的運行時數據,如GC分代年齡,哈希碼即hashCode,鎖狀態標識,線程持有的鎖,偏向線程ID等信息,在這裡我採用的都是文字描述,關於對象頭信息,有一張圖描述的很清楚,自行查閱吧,Mark Word被設計成一個非固定的數據結構,原因在於在極小的內存空間存儲盡量多的信息,它會根據對象的狀態復用自己的存儲空間。
好了,我們繼續吧,第二部分是類型指針,並不是所有的虛擬機都有,由於我們在說hotSpot,類型指針即對象指向它的類元數據的指針,虛擬機通過這個指針來確定對象是哪個類的實例(句柄和直接指針),此外,如果對象是一個Java數組,那麼在對象頭中還必須有一塊用於記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據信息確定java對象的大小,但是從數組的元數組中卻無法確定數組的大小,這塊內容稍顯晦澀難懂,大家有個印象就可以了,想深入了解的查閱對應的資料信息吧。
對齊填充部分不是必然存在的,它僅僅起着佔位符的作用,由於HotSpot虛擬機的自動內存管理機制要求對象的起始地址必須是8位元組的整倍數,因此,當對象的實例數據部分沒有對齊時,這個時候就需要對齊填充來補全了。
ok,這篇文章快要結束了,下面我們在說下一些內容,我們在程序中創建對象是為了使用對象,Java程序需要通過棧上的引用來操作堆上的具體對象,目前主流的訪問方式有使用句柄和直接指針兩種,如果使用句柄訪問的話,那麼Java堆中將會劃分一塊內存來作為句柄池,reference中存儲的就是對象的句柄池地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。如果使用直接指針訪問,那麼Java堆對象的布局中就必須考慮如何防止訪問類型數據的相關信息了,而reference中存儲的直接就是對象地址。