《深刻理解Java虛擬機:JVM高級特性與最佳實踐(第二版》讀書筆記與常見面試題總結java
本節常見面試題:程序員
介紹下Java內存區域(運行時數據區)。面試
對象的訪問定位的兩種方式。算法
對於Java程序員來講,在虛擬機自動內存管理機制下,再也不須要像C/C++程序開發程序員這樣爲內一個new 操做去寫對應的delete/free操做,不容易出現內存泄漏和內存溢出問題。正是由於Java程序員把內存控制權利交給Java虛擬機,一旦出現內存泄漏和溢出方面的問題,若是不瞭解虛擬機是怎樣使用內存的,那麼排查錯誤將會是一個很是艱鉅的任務。數組
Java虛擬機在執行Java程序的過程當中會把它管理的內存劃分紅若干個不一樣的數據區域。緩存
程序計數器是一塊較小的內存空間,能夠看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等功能都須要依賴這個計數器來完。微信
另外,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各線程之間計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。函數
與程序計數器同樣,Java虛擬機棧也是線程私有的,它的生命週期和線程相同,描述的是Java方法執行的內存模型。佈局
Java內存能夠粗糙的區分爲堆內存(Heap)和棧內存(Stack),其中棧就是如今說的虛擬機棧,或者說是虛擬機棧中局部變量表部分。性能
局部變量表主要存放了編譯器可知的各類數據類型、對象引用。
和虛擬機棧所發揮的做用很是類似,區別是: 虛擬機棧爲虛擬機執行Java方法 (也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。
Java虛擬機所管理的內存中最大的一塊,Java堆是全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例以及數組都在這裏分配內存。
Java堆是垃圾收集器管理的主要區域,所以也被稱做GC堆(Garbage Collected Heap).從垃圾回收的角度,因爲如今收集器基本都採用分代垃圾收集算法,因此Java堆還能夠細分爲:新生代和老年代:在細緻一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。
方法區與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即便編譯器編譯後的代碼等數據。
HotSpot虛擬機中方法區也常被稱爲 「永久代」,本質上二者並不等價。僅僅是由於HotSpot虛擬機設計團隊用永久代來實現方法區而已,這樣HotSpot虛擬機的垃圾收集器就能夠像管理Java堆同樣管理這部份內存了。可是這並非一個好主意,由於這樣更容易遇到內存溢出問題。
相對而言,垃圾收集行爲在這個區域是比較出現的,但並不是數據進入方法區後就「永久存在」了。
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池信息(用於存放編譯期生成的各類字面量和符號引用)
直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用。並且也可能致使OutOfMemoryError異常出現。
JDK1.4中新加入的NIO(New Input/Output)類,引入了一種基於通道(Channel) 與緩存區(Buffer) 的I/O方式,它能夠直接使用Native函數庫直接分配堆外內存,而後經過一個存儲在java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣就能在一些場景中顯著提升性能,由於避免了在Java堆和Native堆之間來回複製數據。
本機直接內存的分配不會收到Java堆的限制,可是,既然是內存就會受到本機總內存大小以及處理器尋址空間的限制。
經過上面的介紹咱們大概知道了虛擬機的內存狀況,下面咱們來詳細的瞭解一下HotSpot虛擬機在Java堆中對象分配、佈局和訪問的全過程。
虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,而且檢查這個符號引用表明的類是否已被加載過、解析和初始化過。若是沒有,那必須先執行相應的類加載過程。
在類加載檢查經過後,接下來虛擬機將爲新生對象分配內存。對象所需的內存大小在類加載完成後即可肯定,爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。分配方式有 「指針碰撞」 和 「空閒列表」 兩種,選擇那種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。
虛擬機採用CAS配上失敗重試的方式保證更新操做的原子性。
接下來,虛擬機要對對象進行必要的設置,例如這個對象是那個類的實例、如何才能找到類的元數據信息、對象的哈希嗎、對象的GC分代年齡等信息。這些信息存放在對象頭中,根據虛擬機當前運行狀態的不一樣,如是否啓用偏向鎖等,對象頭會與不一樣的設置方式。
new指令執行完後,再按照程序員的意願執行init方法後一個真正可用的對象才誕生。
在Hotspot虛擬機中,對象在內存中的佈局能夠分爲3快區域:對象頭、實例數據和對齊填充。
Hotspot虛擬機的對象頭包括兩部分信息,第一部分用於存儲對象自身的自身運行時數據(哈希嗎、GC分代年齡、鎖狀態標誌等等),另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是那個類的實例。
實例數據部分是對象真正存儲的有效信息,也是在程序中所定義的各類類型的字段內容。
對齊填充部分不是必然存在的,也沒有什麼特別的含義,僅僅起佔位做用。 由於Hotspot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或2倍),所以,當對象實例數據部分沒有對齊時,就須要經過對齊填充來補全。
創建對象就是爲了使用對象,咱們的Java程序經過棧上的reference數據來操做堆上的具體對象。對象的訪問方式有虛擬機實現而定,目前主流的訪問方式有①使用句柄和②直接指針兩種:
這兩種對象訪問方式各有優點。使用句柄來訪問的最大好處是reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference自己不須要修改。使用直接指針訪問方式最大的好處就是速度快,它節省了一次指針定位的時間開銷。
歡迎關注個人微信公衆號:"Java面試通關手冊"(一個有溫度的微信公衆號,期待與你共同進步~~~堅持原創,分享美文,分享各類Java學習資源):