Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進來,牆裏面的人想出去。
一.運行時數據區域
Java虛擬機在執行Java程序的過程中會把它所管理的內存區域劃分爲若干個不同的數據區域。如下圖所示:
1.1 程序計數器
作用:看作當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等。每個線程都有一個獨立的的程序計數器,各條線程之間計數器互不影響。
1.2 Java虛擬機棧
虛擬機棧描述的是Java方法執行的內存模型,每個方法在執行的同時會創建一個棧幀,用於存儲局部變量表,操作數棧,動態鏈接,方法出口燈信息。每一個犯法從調用直至完成的過程,就對應一個棧幀在虛擬機棧中入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數據類型(Boolean,byte,char,short,int,float,long,double),對象引用(reference類型,它不等同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和return Address類型(指向了一條字節碼指令的地址)
1.3 本地方法棧
區別:虛擬機棧爲虛擬機執行Java方法服務,本地方法棧爲虛擬機使用到的Native方法服務
1.4 Java堆
作用:所有的對象實例以及數組都要在堆上分配,也是垃圾收集器管理的主要區域
1.5 方法區
作用:用於存儲已被虛擬機加載的類信息,常亮,靜態變量,即時編譯器編譯後的代碼等數據。
1.6 運行時常量池
作用:是方法區的一部分。Class文件中除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行常量池中存放。
1.7 直接內存
並不是虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域。
二. HotSpot虛擬機對象探祕
HotSpot虛擬機在Java堆中對象分配、佈局、訪問的全過程
2.1 對象的創建
-
通過new指令去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,檢查這個符號引用代表的類是否已被加載、解析、和初始化過。
-
在類加載通過後,虛擬機爲新生對象分配內存。有兩種方式:一、“指針碰撞”:如果java堆中的內存是規整的,所有用過的內存都放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器,通過指針向空閒那邊挪動一段與對象大小相等的距離。二、“空閒列表”:Java堆中的內存並不是規整的,已使用的內存和空閒的內存相互交錯,虛擬機通過維護一個列表,記錄內存塊,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。
- 內存分配完以後,虛擬機需要將分配到的內存空間都初始化爲零值。
2.2 對象的內存佈局
- 對象頭:用於存儲對象自身的運行時數據.如哈希碼、GC分代年齡、鎖狀態標誌等等。
- 實例數據:對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。
- 對齊填充:佔位符的作用。
2.3 對象的訪問定位
- 句柄訪問:通過Java堆劃分出一塊內存來作爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。
- 直接指針訪問:Java堆對象的佈局中就必須考慮如何防止訪問類型數據的相關信息,而reference中存儲的直接就是對象地址。
三. OutOfMemoryError
- Java堆溢出;
- 虛擬機棧和本地方法棧溢出;一:如果線程請求的深度大於虛擬機所允許的最大深度,將拋出StackOverflowError;二:如果虛擬機在擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError;
- 方法區和運行時常量池溢出;
- 本機直接內存溢出;