jvm深入理解
一 概要(本文主要參考深入淺出jvm)
1.什麼是jvm?
2.jvm是如何分配記憶體的?
3.jvm是如何保證垃圾正確回收的?
4.如何監控和優化gc?
什麼是jvm(Java Virtual Machine),jvm是jre的一部分,大家都知道jre是java執行環境由jvm和javaapi組成。jvm通過載入和編譯java檔案並通過javaApi進行執行。
既然java宣稱一次編譯到處執行,那麼它怎麼實現的呢?
我們先來看一下java載入順序

由上圖我們可以看出,不論在什麼環境下只要有對應的jvm就可以執行。比如windows,linux下的虛擬機器都可以執行class檔案
下面我們來看一下jvm的組成
1.程式計數器
程式計數器是一塊較小的記憶體空間,位元組碼直譯器在工作時就是通過這個計數器的值來選取下一次需要執行的位元組碼指令。
分支,異常處理,迴圈等都需要依賴程式計數器來完成。
程式計數器是私有的
為什麼呢?因為java虛擬機器的多執行緒是通過執行緒間的切換,在任何一個時刻一個處理器只會處理一條執行緒中一條指令。所以為了保證執行緒切換後能正確執行,每條執行緒都需要一個單獨的程式計數器。因此小程式計數器是私有的。
2.虛擬機器棧
java虛擬機器棧也是私有的,生命週期與執行緒相同虛擬機器棧描述的是Java 方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。
虛擬機器棧中的區域性變量表
區域性變量表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference 型別,不等同於物件本身,根據不同的虛擬機器實現,它可能是一個指向物件起始地址的引用指標,也可能指向一個代表物件的控制代碼或者其他與此物件相關的位置)和returnAddress 型別(指向了一條位元組碼指令的地址)。其中64 位長度的long 和double 型別的資料會佔用2 個區域性變數空間(Slot),其餘的資料型別只佔用1 個。區域性變量表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變量表的大小。在Java 虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError 異常;如果虛擬機器棧可以動態擴充套件(當前大部分的Java 虛擬機器都可動態擴充套件,只不過Java 虛擬機器規範中也允許固定長度的虛擬機器棧),當擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryError 異常。
3.本地方法棧
本地方法棧與虛擬機器棧所發揮的作用是非常相似的,區別就是虛擬機器棧執行的是java方法,而本地方法棧執行的是native方法
4.java堆
堆記憶體是Java虛擬機器中共享的最大的一塊記憶體,java虛擬機器中啟動時建立。該記憶體存放的是java例項物件。所有的例項物件和陣列都在這一塊分配。堆記憶體是jvm虛擬機器垃圾回收的主要區域,因此也成為gc堆。
5.方法區
方法區與堆記憶體一樣也是共享的一塊區域,它主要存放已被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。根據Java 虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError 異常。
二 java虛擬機器中物件的訪問及存放
舉個例項Student stu=new Student();
這份程式碼中Student stu是一個引用變數所以存放在java虛擬機器棧上,new Student()是一個例項物件存放在java堆上。另外,在Java 堆中還必須包含能查詢到此物件型別資料(如物件型別、父類、實現的介面、方法等)的地址資訊,這些型別資料則儲存在方法區中。
由於reference 型別在Java 虛擬機器規範裡面只規定了一個指向物件的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java 堆中的物件的具體位置,因此不同虛擬機器實現的物件訪問方式會有所不同,主流的訪問方式有兩種:使用控制代碼和直接指標。如果使用控制代碼訪問方式Java 堆中將會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料和型別資料各自的具體地址資訊,如下圖所示。

指標方式
Java 堆物件的佈局中就必須考慮如何放置訪問型別

這兩種物件的訪問方式各有優勢,使用控制代碼訪問方式的最大好處就是reference 中儲存的是穩定的控制代碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制代碼中的例項資料指標,而引用物件本身不需要被修改。使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開銷,由於物件的訪問在Java 中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。