過年會把《深入了解 Java 虛拟機》回看一遍,整理下知識點
C/C++
的記憶體管理都在編碼人員自己的手裡進行控制,
delete/free
雖能讓人感受到上帝視角的快感,卻也加大了對編碼人員的考驗。
Java
、
Golang
、
Nodejs
等現代語言因為有虛拟機這層,是以将記憶體管理的工作移交給了虛拟機做。
JVM 運作時記憶體區域
每個區域的功能如下:
對象的建立
我們經常使用
new
來建立一個強引用的對象,那麼
JVM
層面如何處理對象的建立流程呢?
首先,
JVM
會先根據這個指令的參數,看能否在常量池中定位到一個符号引用,并檢查引用代表的類是不是已經被加載、解析、初始化過了,沒有的話要執行類加載過程。類加載檢查通過後,要為對象配置設定記憶體,如果采用了具有 “壓縮-整理” 功能的
GC
,那麼新生代記憶體區域是歸整的(即用過的在一邊,空閑的在另一邊),是以配置設定記憶體隻是簡單地将臨界指針向空閑區域移動一個對象的大小,這稱為
指針碰撞
;如果是類似
标記回收
這種算法的
GC
,那麼記憶體區域是塊狀的,能否找到合适的空閑記憶體依靠于
JVM
維護的空閑清單,這種方式稱之為
空閑清單法
。
不同的
GC
的特點請見我之前的文章 Java 常見的垃圾收集器總結。
JVM
建立對象是頻繁的過程,如何解決多線程情況下,配置設定記憶體時的沖突問題呢?一種方式是加鎖強制同步,但是效率太低,另一種是
CAS
失敗重試來保證原子性;還有一種是預配置設定,每個線程先預配置設定一塊記憶體稱為本地線程配置設定緩沖(
TLAB
),當
TLAB
用完時再選擇新的
TLAB
(這時再加鎖)。
對象的記憶體布局
對象通路
Java 程式需要通過棧上的引用資料來操作具體對象,而至于引用是如何去定位、通路堆中對象的,實際虛拟機實作時有兩種方式:
兩種通路方式各有優勢,句柄通路方式最大的好處是引用中存放的是穩定的句柄位址,在對象被移動時,隻會改變句柄中的執行個體資料指針,而引用本身不需要被修改。
而指針通路的最大優勢是速度快,它節省了一次指針定位的開銷,由于對象通路在
Java
程式中非常頻繁,這類開銷積少成多後也是一項非常可觀的成本。
具體的通路方式都是有虛拟機指定的,虛拟機Sun HotSpot使用的是直接指針方式,不過從整個軟體開發的範圍來看,各種語言和架構使用句柄通路方式的情況十分常見。
舉個例子說明
這段代碼大家都很熟悉,假設這段代碼出現在方法體中,那麼
Object obj
部分的語義将會反映到
Java 棧
的本地變量表中,作為一個
reference
類型的資料存在。而
new Object();
部分的語義将在類加載過程後反應到
Java
堆和方法區中,前者形成一塊存儲
Object
類型所有執行個體資料值
(Instance Data)
的結構化記憶體,後者則存儲
Object
類的對象類型資料。
參考資料
-《深入了解Java虛拟機》第二章,周志明著