new一個對象,內存到底做了些什麼?

本文是學習《深入理解JAVA虛擬機》的學習筆記(1)

虛擬機調優,貌似是一個高深的技能。《深入理解JAVA虛擬機》,在程序員世界裏,肯定是一本推薦閱讀的書。萬丈高樓平地起,從最基礎的開始,走近這部大作吧!

Java虛擬機運行時數據區

虛擬機運行時,內存會被劃分爲多個區,什麼堆呀,棧呀,方法區呀。看下圖,來徹底搞清楚內存的分區。
在這裏插入圖片描述
程序計數器(Program counter Register)
當前線程所執行的字節碼程序的行號指示器。線程切換後,能恢復到正確的執行位置,每條線程需要有一個獨立的程序計數器。(運行Native方法,計數器值爲空),唯一一個不會報空指針的區域。可籠統的認爲,就是記住代碼執行到哪一行了。

Java 虛擬機棧(Java Virtual Machine Stacks)
線程私有,生命週期與線程相同。每個方法執行時,都會開闢一個棧楨(Stack Frame),用於存儲局部變量、操作數棧、動態鏈接、方法出口等等。調用棧的深度過大,拋出StackOverflowError異常,無法申請足夠內存時,拋出OutOfMemoryError異常。比如說我在代碼中計算3+4是多少,這裏的3和4,包括加號,都是在棧楨裏的

本地方法棧(Native Method Stack)
虛擬機調用native方法時開闢的內存空間,若內存不夠,同樣是拋出兩種異常。java類庫中,某某些方法是用C++,這種方法專門開闢一塊內在

Java堆(Java Heap)
所有線程共用的內在區域,用來存放對象實例。其大小是可以實現成固定的,也可以實現成可擴展的(-Xmx和-Xms)。如果堆中沒有內存完成實例分配,並且無法再擴展時,拋出OOM異常。new出來的對象就在這個地方待著

方法區(Method Area)
各線程共用,存儲已被虛擬機加載的類的信息、常量、靜態變量、編譯後的代碼等數據。HotSpot虛擬機,將永久代(Permanent Generation)放在這個區域。此區域內存不不足時,也會拋出OOM。 運行時常量池(Runtime Constant Pool)是方法區的一部分,類加載後,各種字面量和符號引用放入其中。HotSpot永久代就在這裏面

對象的創建

知道了內存的分區,咱再來看下,本篇的正題,對象是怎麼創建的。虛擬機收到一條new指令(創建普通對象)時,首先檢查指令的參數,在常量池中是否可定位到一個類的符號引用,並且檢查這具符號引用代表的類是否已被加載、解析和初始化。如果沒有就執行類加載過程。一句話,先看下之前是不是創建過

類加載檢查通過後,虛擬機將爲新生對象分配內存。若Java堆中對象是絕對規整的,用指針碰撞(Bump the Pointer)的方法,否則使用空閒列表(Free List)的方式分配。Java對象是否規整,又與採用的垃圾收集器有關。 這裏的內容比較多,咱以後文章再詳細說

出於併發安全角度考慮,在正在給A對象分配內存時,指針還沒來及修改,對象B又使用了原來的指針。解決這個問題的兩個方案,其一是同步處理,虛擬機使用CAS配上失敗重試的方式保證操作的原子性。其二是本地線程分配緩衝(Thread Local Allocation Buffer, TLAB)CAS是啥?對就是和樂觀鎖差不多

內在分配之後,虛擬機將分配到的內在空間都初始化爲0(不包括對象頭),此時對象是可以被訪問到的,字段對應的值,就是該數據類型對應的零值。

之後設置對象頭(Object Header),然後 int方法執行,對象創建完成。對象頭回頭單獨拎出來詳細說

對象的內存佈局

對象在內存中的佈局有三個部分, 對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)

對象頭包含兩部分 Mark Word和類型指針。前者存儲對象自身運行的數據,如HashCode、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。後者是虛擬機用來確定此對象屬於哪個類。
32位和64位虛擬機(未開啓壓縮指針),Mark Word 分別佔32bit 和64bit

實例數據中,無論是父類繼承的,還是子類定義的都要記錄下來。(默認是相同寬度的分配在一起)

對齊填充,是HotSpot VM 規定,對象起始地址必需是8字節的整數倍。所有對象實例數據部分沒有對齊時,就需要補全來對齊。

對象的訪問定位

這個直接看圖,很直觀
在這裏插入圖片描述在這裏插入圖片描述
HotSpot VM 中,java棧裏對象引用,是通過直接指針(非句柄)。

好了,第一篇結束,總結下,談了三個問題,1、內存分區,2、對象創建,3、對象的內在佈局與訪問定位

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