大學期間必須知道的JVM知識

真正沒有資格談明天的人,是那個不懂得珍惜今日的人。
你好,我是夢陽辰,期待與你相遇!java

概述

它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。
JVM有本身完善的硬件架構,如處理器、堆棧、寄存器等,還具備相應的指令系統。Java語言最重要的特色就是跨平臺運行。使用JVM就是爲了支持與操做系統無關,實現跨平臺。因此,JAVA虛擬機JVM是屬於JRE的,而如今咱們安裝JDK時也附帶安裝了JRE(固然也能夠單獨安裝JRE)。算法

Java虛擬機主要分爲五大模塊:類裝載器子系統、運行時數據區、執行引擎、本地方法接口和垃圾收集模塊。
JVM是運行在操做系統之上的,它與硬件沒有直接的交互
在這裏插入圖片描述
JVM體系結構概覽
在這裏插入圖片描述
亮色區域:
線程共享編程

存在垃圾回收數組

01.類裝載器ClassLoader

負責加載class文件,class文件在文件開頭有特定的文件標示
將class文件字節碼內容加載到內存中,並將這些內容轉換成方法區中的運行時數據結構而且ClassLoader只負責class文件的加載,至於它是否能夠運行,則由Execution Engine決定。安全

類裝載器相似於快遞公司。
在這裏插入圖片描述
**特定標識:**cafe babe
在這裏插入圖片描述數據結構

類裝載器的種類

虛擬機自帶的加載器架構

啓動類加載器(Bootstrap)
C++擴展類加載器(Extension)
ava應用程序類加載器(AppClassLoader)Java也叫系統類加載器,加載當前應用的classpath的全部類

用戶自定義加載器併發

Java.lang. ClassLoader的子類,用戶能夠定製類的加載方式

在這裏插入圖片描述

類加載器的雙親委派機制

當一個類收到了類加載請求,他首先不會嘗試本身去加載這個類,而是把這個請求委派給父類去完成,每個層次類加載器都是如此,所以全部的加載請求都應該傳送到啓動類加載其中,只有當父類加載器反饋本身沒法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器纔會嘗試本身去加載。編程語言

採用雙親委派的一個好處是好比加載位於rt.jar包中的類java.lang.Object,無論是哪一個加載器加載這個類,最終都是委託給頂層的啓動類加載器進行加載,這樣就保證了使用不一樣的類加載器最終獲得的都是一樣一個Object對象。ide

砂箱安全機制
保證你寫的代碼不會污染java內置的代碼.

執行引擎

主要的執行技術有:解釋,即時編譯,自適應優化、芯片級直接執行其中解釋屬於第一代JVM,即時編譯JIT屬於第二代JVM,自適應優化(目前Sun的HotspotJVM採用這種技術)則吸收第一代JVM和第二代JVM的經驗,採用二者結合的方式 。

自適應優化:開始對全部的代碼都採起解釋執行的方式,並監視代碼執行狀況,而後對那些常常調用的方法啓動一個後臺線程,將其編譯爲本地代碼,並進行仔細優化。若方法再也不頻繁使用,則取消編譯過的代碼,仍對其進行解釋執行。

Execution Engine執行引擎負責解釋命令,提交操做系統執行

02.Native Method Stack(本地方法棧)

它的具體作法是Native Method Stack中登記native方法,在ExecutionEngine執行時加載本地方法庫。

03.Native Interface本地方法接口

本地接口的做用是融合不一樣的編程語言爲Java所用,它的初
衷是融合C/C++程序,Java誕生的時候是C/C++橫行的時候,要想立足,必須有調用C/C++程序,因而就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體作法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。

目前該方法使用的愈來愈少了,除非是與硬件相關的應用,比
如經過Java程序驅動打印機或者Java系統管理生產設備,在企業級應用
中已經比較少見。由於如今的異構領域間的通訊很發達,好比可使用Socket通訊,也可使用Web Service等等,很少作介紹。

04.程序計數器

pc寄存器

每一個線程都有一個程序計數器,是線程私有的,就是一個指針,
指向方法區中的方法字節碼(用來存儲指向下一條指令的地址,也即將要執行的指令代碼),由執行引擎讀取下一條指令,是一個很是小的內存空間,幾乎能夠忽略不記。

這塊內存區域很小,它是當前線程所執行的字節碼的行號指示器,字節碼解釋器經過改變這個計數器的值來選取下一條須要執行的字節碼指令。若是執行的是一個Native方法,那這個計數器是空的。

用以完成分支、循環、跳轉、異常處理、線程恢復等基礎功能。不會發生內存溢出(OutOfMemory=OOM)錯誤。

05.Method Area方法區

線程共享

存在垃圾回收

方法區:
供各線程共享的運行時內存區域。它存儲了每個類的結構信息(類模板),例如運行時常量池(Runtime Constant Pool)、字段和方法數據、構造函數和普通方法的字節碼內容。上面講的是規範,在不一樣虛擬機裏頭實現是不同的,最典型的就是永久代(PermGen space)和元空間(Metaspace)。

But

實例變量存在堆內存中,和方法區無關。

stack
棧管運行,堆管存儲

棧也叫棧內存,主管Java程序的運行,是在線程建立時建立,它
的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來講不存在垃圾回收問題,只要線程一結束該棧就Over,生命週期和線程一致,是線程私有的。8種基本類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配。

棧存儲什麼?
棧幀中主要保存3類數據:

本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量;

棧操做(Operand Stack):記錄出棧、入棧的操做;

棧幀數據(Frame Data):包括類文件、方法等等。

棧運行原理:
棧中的數據都是以棧幀(Stack Frame)的格式存在,棧幀是一個內存區塊,是一個數據集,是一個有關方法(Method)和運行期數據的數據集,當一個方法A被調用時就產生了一個棧幀F1,並被壓入到棧中,
A方法又調用了B方法,因而產生棧幀F2也被壓入棧,
B方法又調用了C方法,因而產生棧幀F3也被壓入棧,………
執行完畢後,先彈出F3棧幀,再彈出F2棧幀,再彈出F1棧幀……
遵循「先進後出」/「後進先出」原則。

每一個方法執行的同時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息,每個方法從調用直至執行完畢的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。棧的大小和具體JVM的實現有關,一般在256K~756K之間,與等於1Mb左右。

在這裏插入圖片描述

每執行一個方法都會產生一個棧幀,保存到棧(後進先出)的頂部,頂部棧就是當前的方法,該方法執行完畢後會自動將此棧幀出棧。
在這裏插入圖片描述

在這裏插入圖片描述

HotSpot是使用指針的方式來訪問對象:Java堆中會存放訪問類的元數據的地址,reference存儲的就直接是對象的地址

06.Heap堆

一個JVM實例只存在一個堆內存,堆內存的大小是能夠調節的。

類加載器讀取了類文件後,須要把類、方法、常變量放到堆內存中,保存全部引用類型的真實信息,以方便執行器執行,堆內存分爲三部分:

Young Generation Space 新生區  Young/New
Tenure generation space養老區  old/ Tenure
Permanent Space  永久區  Perm

Java7以前
一個JVM實例只存在一個堆內存,堆內存的大小是能夠調節的。
類加載器讀取了類文件後,須要把類、方法、常變量放到堆內存中,保存全部引用類型的真實信息,以方便執行器執行。
在這裏插入圖片描述
o1d養老區,滿了,開啓FuliGC = FGC

Full GC 屢次,發現養老區空間沒辦法騰出來,OOM

Java8爲元空間,不是永久代

物理上:新生+養老

新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應
用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分:伊甸區(Eden space)和倖存者區(Survivor pace),全部的類都是在伊甸區被new出來的。倖存區有兩個:0區(Survivor 0 space)和1區
(Survivor 1 space)。當伊甸園的空間用完時,程序又須要建立對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的再也不被其餘對象所引用的對象進行銷燬。而後將伊甸園中的剩餘對象移動到倖存0區。若倖存0區也滿了,再對該區進行垃圾回收,而後移動到1區。那若是1區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生MajiorGC (Eul1GC),進行養老區的內存清理。若養老區執行了Full GC以後發現依然沒法進行對象的保存,就會產生OOM異常「OutOfMemoryExror」。

若是出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。
緣由有二:
(1)Java虛擬機的堆內存設置不夠,能夠經過參數-Xms、-Xmx來調整。(2)代碼中建立了大量大對象,而且長時間不能被垃圾收集器收集(存在被引用)。
在這裏插入圖片描述
from區和to區,他們的位置和名分,不是固定的,每次GC後會交換GC以後有交換,誰空誰是to

MinorGC的過程(複製>清空>互換)

1: eden、SurvivorFrom複製到Survivorlo,年齡+1

首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到SurvivorFrom區,當Eden區再次觸發GC的時候會掃描Eden區和From區域,對這兩個區域進行垃圾回收,通過此次回收後還存活的對象,則直接複製到To區域(若是有對象的年齡已經達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1

2:清空eden、SurvivorFrom
而後,清空Eden和SurvivorFrom中的對象,也即複製以後有交換,誰空誰是to

3: SurvivorTo和 SurvivorErom互換
最後,SurvivorTo和SurvivorFrom互換,原SurvivorTo成爲下一次GC時的SurvivorFrom區。部分對象會在From和To區域中複製來複制去,如此交換15次(由VM參數MaxTenuringThreshold決定,這個參數默認是15),最終若是仍是存活,就存入到老年代。
在這裏插入圖片描述
實際而言,方法區(Method Area)和堆同樣,是各個線程共享的內
存區域,它用於存儲虛擬機加載的:類信息+普一般量+靜態常量+編譯器編譯後的代碼等等,雖然JVM規範將方法區描述爲堆的一個邏輯部分,但它卻還有一個別名叫作Non-Heap(非堆),目的就是要和堆分開。

對於HotSpot虛擬機,不少開發者習慣將方法區稱之爲「永久代
Parmanent Gen)」,但嚴格本質上說二者不一樣,或者說使用永久代來實現方去區而已,永久代是方法區(至關因而一個接口interface)的一個實現,jdk1.7的反本中,已經將本來放在永久代的字符串常量池移走。
在這裏插入圖片描述
永久區(java7以前有)
永久存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的
Class,Interface 的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM纔會釋放此區域所佔用的內存。

07.堆參數調優

在這裏插入圖片描述
在這裏插入圖片描述
在Java8中,永久代已經被移除,被一個稱爲元空間的區域所取代。元空間的本質和永久代相似。

元空間與永久代之間最大的區別在於:
永久帶使用的JVM的堆內存,可是java8之後的元空間並不在虛擬機中而是使用本機物理內存。

所以,默認狀況下,元空間的大小僅受本地內存限制。類的元數據放入native memory,字符串池和類的靜態變量放入java堆中,這樣能夠加載多少類的元數據就再也不由MaxPermsize控制,而由系統的實際可用空間來控制。
在這裏插入圖片描述
GC:
在這裏插入圖片描述
FullGC:
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
JVM在進行GC時,並不是每次都對上面三個內存區域一塊兒回收的,大部分時候回收的都是指新生代。

所以GC按照回收的區域又分了兩種類型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC)

Minor GC和IFull GC的區別
普通GC(minor GC):只針對新生代區域的GC,指發生在新生代的垃圾收集動做,由於大多數Java對象存活率都不高,因此Minor GC很是頻繁,通常回收速度也比較快。
全局GC(major GC or Full GC):指發生在老年代的垃圾收集動做,出現了Major GC,常常會伴隨至少一次的Minor GC(但並非絕對的)。Major GC的速度通常要比Minor GC慢上10倍以上

判斷對象是否已經死亡的算法:引用計數算法,可達性分析算法;

四個垃圾收集算法:標記清除算法,複製算法,標記整理算法,分代收集算法;

七個垃圾收集器:Serial,SerialOld,ParNew,Parallel Scavenge,Parallel Old,CMS,G1.

引用計數法

在這裏插入圖片描述
在 Java 中,引用與對象相關聯,若是要操做對象,則必須使用引用。所以,能夠經過引用計數來肯定對象是否能夠回收。實現原則是,若是一個對象被引用一次,計數器 +1,反之亦然。當計數器爲 0 時,該對象不被引用,則該對象被視爲垃圾,而且能夠被 GC 回收利用。

複製算法

年輕代:

Minor GC會把Eden中的全部活的對象都移到Survivor區域中,若是Survivor區中放不下,那麼剩下的活的對象就被移到Oldgeneration中,也即一旦收集後,Eden是就變成空的了。

當對象在Eden (包括一個 Survivor區域,這裏假設是from 區域)出生後,在通過一次 Minor GC後,若是對象還存活,而且可以被另一塊Survivor區域所容納(上面已經假設爲from 區域,這裏應爲 to區域,即 to區域有足夠的內存空間來存儲Eden和from區域中存活的對象),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor區域(即 to區域)中,而後清理所使用過的Eden 以及 Survivor區域(即from區域),而且將這些對象的年齡設置爲1,之後對象在Survivor 區每熬過一次MinorGC,就將對象的年齡+1,當對象的年齡達到某個值時(默認是15歲,經過-XX:MaxTenuringThreshold 來設定參數),這些對象就會成爲老年代。

-XX:MaxTenuringThreshold一設置對象在新生代中存活的次數

HotSpot JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例爲8:1:1,通常狀況下,新建立的對象都會被分配到Eden區(一些大對象特殊處理),這些對象通過第一次Minor GC後,若是仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增長1歲,當它的年齡增長到必定程度時,就會被移動到年老代中。由於年輕代中的對象基本都是朝生夕死的(90%以上),因此在年輕代的垃圾回收算法使用的是複製算法,複製算法的基本思想就是將內存分爲兩惚,每次只用其中一塊,當這一塊內存用完,就將還活着的對象複製到另一塊上面。複製算法不會產生內存碎片。

在這裏插入圖片描述

在GC開始的時候,對象只會存在於Eden區和名爲「From」的Survivor區,Survivor區「To」是空的。緊接着進行GC,Eden區中全部存活的對象都會被複制到「To」,而在「From」區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到必定值(年齡閾值,能夠經過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到「To」區域。通過此次GC後,Eden區和From區已經被清空。這個時候,「From」和「To」會交換他們的角色,也就是新的「To」就是上次GC前的「From」,新的「From"1就是上次GC前的「To」。無論怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,「To」區被填滿以後,會將全部對象移動到年老代中。
在這裏插入圖片描述
由於Eden區對象通常存活率較低。通常的,使用兩塊10%的內存做爲空閒和活動區間,而另外80%的內存,則是用來給新建對象分配內存的。一旦發生GC,將10%的from活動區間與另外80%中存活的eden對象轉移到10%的to空閒區間,接下來,將以前90%的內存所有釋放,以此類推。

複製算法它的缺點也是至關明顯的。
一、它浪費了一半的內存,這太要命了。

二、若是對象的存活率很高,咱們能夠極端一點,假設是100%存活,那麼咱們須要將全部對象都複製一遍,並將全部引用地址重置一遍。複製這一工做所花費的時間,在對象存活率達到必定程度時,將會變的不可忽視。因此從以上描述不難看出,複製算法要想使用,最起碼對象的存活率要很是低才行,並且最重要的是,咱們必需要克服50%內存的浪費

標記清除算法(Mark-Sweep)

老年代通常是由標記清除或者是標記清除與標記整理的混合實現
在這裏插入圖片描述

解決了內存空間浪費的問題,可是會出現內存碎片,而且速度慢。

用通俗的話解釋一下標記清除算法,就是當程序運行期間,若可使用的內存被耗盡的時候,GC線程就會被觸發並將程序暫停,隨後將要回收的對象標記一遍,最終統一回收這些對象,完成標記清理工做接下來便讓應用程序恢復運行。|

在這裏插入圖片描述

標記壓縮算法(Mark-ComPact)

在這裏插入圖片描述
耗時長

在整理壓縮階段,再也不對標記的對像作回收,而是經過全部存活對像都向一端移動,而後直接清除邊界之外的內存。
能夠看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可,這比維護一個空閒列表顯然少了許多開銷。
標記/整理算法不只能夠彌補標記/清除算法當中,內存區域分散的缺點,也消除了複製算法當中,內存減半的高額代價
在這裏插入圖片描述
內存效率:複製算法>標記清除算法>標記整理算法(此處的效率只是簡單的對比時間複雜度,實際狀況不必定如此)。內存整齊度:複製算法=標記整理算法>標記清除算法。
內存利用率:標記整理算法=標記清除算法>複製算法。
能夠看出,效率上來講,複製算法是當之無愧的老大,可是卻浪費了太多內存,而爲了儘可能兼顧上面所提到的三個指標,標記/整理算法相對來講更平滑一些,但效率上依然不盡如人意,它比複製算法多了一個標記的階段,又比標記/清除多了一個整理內存的過程

難道就沒有一種最優算法嗎?
回答:無,沒有最好的算法,只有最合適的算法。
分代收集算法。

年輕代(Young Gen)

年輕代特色是區域相對老年代較小,對像存活率低。
這種狀況複製算法的回收整理,速度是最快的。複製算法的效率只和當前存活對像大小有關,於是很適用於年輕代的回收。而複製算法內存利用率不高的問題,經過hotspot中的兩個survivor的設計獲得緩解。|

老年代(Tenure Gen)

老年代的特色是區域較大,對像存活率高。
這種狀況,存在大量存活率高的對像,複製算法明顯變得不合適。通常是由標記清除或者是標記清除與標記整理的混合實現。

Mark階段的開銷與存活對像的數量成正比,這點上:說來,對於老年代,標記清除或者標記整理有一些不符,但能夠經過多核/線程利用,對併發、並行的形式提標記效率。

Sweep階段的開銷與所管理區域的大小形正相關,但Sweep「就地處決」的特色,回收的過程沒有對像的移動。使其相對其它有對像移動步驟的回收算法,仍然是效率最好的。可是須要解決內存碎片問題。

Compact階段的開銷與存活對像的數據成開比,如上一條所描述,對於大量對像的移動是很大開銷的,作爲老年代的第一選擇並不合適。

基於上面的考慮,老年代通常是由標記清除或者是標記清除與標記整理的混合實現。

08.JMM

volatile是java虛擬機提供的輕量級的同步機制。

在變量前加volatile,使得各個線程可見。

1.可見性

2.原子性

3.VolatileDemo代碼演示可見性

4.有序性

JMM關於同步的規定:
1線程解鎖前,必須把共享變量的值刷新回主內存

2線程加鎖前,必須讀取主內存的最新值到本身的工做內存

3加鎖解鎖是同一把鎖

JMM(Java內存模型Java Memory Model,簡稱JMM)自己是一種抽象的概念並不真實存在,它描述的是一組規則或規範,經過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。

因爲JVM運行程序的實體是線程,而每一個線程建立時JVM都會爲其建立一個工做內存(有些地方稱爲棧空間),工做內存是每一個線程的私有數據區域,而Java內存模型中規定全部變量都存儲在主內存,主內存是共享內存區域,全部線程均可以訪問,但線程對變量的操做(讀取賦值等)必須在工做內存中進行,首先要將變量從主內存拷貝到的線程本身的工做內存空間,而後對變量進行操做,操做完成後再將變量寫回主內存,不能直接操做主內存中的變量,各個線程中的工做內存中存儲着主內存中的變量副本拷貝,所以不一樣的線程間沒法訪問對方的工做內存,線程間的通訊(傳值)必須經過主內存來完成,其簡要訪問過程以下圖:
在這裏插入圖片描述

To the time to life, rather than to life in time to the time to life, rather than to life in time.

在這裏插入圖片描述
在這裏插入圖片描述

相關文章
相關標籤/搜索