JVM初認識
- 2020 年 9 月 16 日
- 筆記
運行時數據區域
-
程式計數器:程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。在虛擬機的概念模型里,位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。是執行緒私有」的記憶體。
-
Java虛擬機棧:與程式計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命周期與執行緒相同。虛擬機棧描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會創建一個棧幀 ,用於存儲局部變數表、操作數棧、動態鏈接、方法出口等資訊。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。
-
本地方法棧:本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。
-
Java堆:對於大多數應用來說,Java堆是Java虛擬機所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機啟動時創建。此記憶體區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配記憶體。
-
方法區:方法區用於存儲已被虛擬機載入的類資訊、常量、靜態變數,如static修飾的變數載入類的時候就被載入到方法區中。運行時常量池是方法區的一部分,class文件除了有類的欄位、介面、方法等描述資訊之外,還有常量池用於存放編譯期間生成的各種字面量和符號引用。在老版jdk,方法區也被稱為永久代。在1.8之後,由於永久代記憶體經常不夠用或發生記憶體泄露,爆出異常java.lang.OutOfMemoryError,所以在1.8之後廢棄永久代,引入元空間的概念。元空間是方法區的在HotSpot jvm 中的實現,元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地記憶體。理論上取決於32位/64位系統可虛擬的記憶體大小。可見也不是無限制的,需要配置參數。
分代回收
HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。因為年輕代中的對象基本都是朝生夕死的,所以在年輕代的垃圾回收演算法使用的是複製演算法,複製演算法的基本思想就是將記憶體分為兩塊,每次只用其中一塊,當這一塊記憶體用完,就將還活著的對象複製到另外一塊上面。複製演算法不會產生記憶體碎片。在GC開始的時候,對象只會存在於Eden區和名為「From」的Survivor區,Survivor區「To」是空的。緊接著進行GC,Eden區中所有存活的對象都會被複制到「To」,而在「From」區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到「To」區域。經過這次GC後,Eden區和From區已經被清空。這個時候,「From」和「To」會交換他們的角色,也就是新的「To」就是上次GC前的「From」,新的「From」就是上次GC前的「To」。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,「To」區被填滿之後,會將所有對象移動到年老代中。
動態年齡計算
Hotspot在遍歷所有對象時,按照年齡從小到大對其所佔用的大小進行累積,當累積的某個年齡大小超過了survivor區的一半時,取這個年齡和MaxTenuringThreshold中更小的一個值,作為新的晉陞年齡閾值。JVM引入動態年齡計算,主要基於如下兩點考慮:
-
如果固定按照MaxTenuringThreshold設定的閾值作為晉陞條件:a)MaxTenuringThreshold設置的過大,原本應該晉陞的對象一直停留在Survivor區,直到Survivor區溢出,一旦溢出發生,Eden+Svuvivor中對象將不再依據年齡全部提升到老年代,這樣對象老化的機制就失效了。b)MaxTenuringThreshold設置的過小,「過早晉陞」即對象不能在新生代充分被回收,大量短期對象被晉陞到老年代,老年代空間迅速增長,引起頻繁的Major GC。分代回收失去了意義,嚴重影響GC性能。
-
相同應用在不同時間的表現不同:特殊任務的執行或者流量成分的變化,都會導致對象的生命周期分布發生波動,那麼固定的閾值設定,因為無法動態適應變化,會造成和上面相同的問題。
常見的垃圾回收機制
-
引用計數法:引用計數法是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用計數器,當有引用連接至對象時,引用計數加1。當引用離開作用域或被置為null時,引用計數減1。雖然管理引用計數的開銷不大,但這項開銷在整個程式生命周期中將持續發生。垃圾回收器會在含有全部對象的列表上遍歷,當發現某個對象引用計數為0時,就釋放其佔用的空間。
-
可達性分析演算法:這個演算法的基本思路就是通過一系列的稱為「GC Roots」的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。