jvm是什么,百度百科這樣寫道:
虛擬機是一種抽象化的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。Java虛擬機有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。Java虛擬機屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息,使得Java程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。
簡單通俗的說,jvm類似一個中間件,為程序和各個操作系統(tǒng)之間架起了一個橋梁,不需要為特定的系統(tǒng)編寫特定的代碼。
jvm內(nèi)存模型
jvm主要包括五大模塊,類裝載器子系統(tǒng)、運行時數(shù)據(jù)區(qū)、執(zhí)行引擎、本地方法接口和垃圾收集模塊。
在本節(jié)中主要講解運行時數(shù)據(jù)區(qū)的數(shù)據(jù)結(jié)構(gòu)。

- 程序計數(shù)器
- 是線程私有的,各個線程之間計數(shù)器互不影響,獨立存儲
- 如果線程執(zhí)行的是java方法,則記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果執(zhí)行的是Native方法,這個計數(shù)器值為空。
- 是唯一一個在jvm規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域
- 虛擬機棧
- 線程私有的,生命周期與線程相同
- 當執(zhí)行一個方法時,都會創(chuàng)建一個棧幀
-
可以存儲局部變量表、操作數(shù)棧、動態(tài)鏈接和方法出口等
2.png
- 本地方法棧
- 線程私有的
- 與虛擬機棧的作用類似,當執(zhí)行Native方法時會用到
- java堆
- 是內(nèi)存管理中最大的一塊
- 存放的是對象的實例和數(shù)組對象
- java堆按照生命周期的不同,劃分為新生代和老年代。當新生代中經(jīng)過多次垃圾回收仍然存活的對象九華轉(zhuǎn)化成老年代 。
- 年輕代又分為Eden和Survivor區(qū)。Survivor區(qū)由FromSpace和ToSpace組成。Eden區(qū)占大容量,Survivor兩個區(qū)占小容量,默認比例是8:1:1
- 新生成的對象首先放到年輕代Eden區(qū),當Eden空間滿了,觸發(fā)Minor GC,存活下來的對象移動到Survivor0區(qū),Survivor0區(qū)滿后觸發(fā)執(zhí)行Minor GC,Survivor0區(qū)存活對象移動到Suvivor1區(qū),這樣保證了一段時間內(nèi)總有一個survivor區(qū)為空。經(jīng)過多次Minor GC仍然存活的對象移動到老年代。
- 老年代存儲長期存活的對象,占滿時會觸發(fā)Major GC = Full GC,GC期間會停止所有線程等待GC完成,
- 將對象根據(jù)存活概率進行分類,對存活時間長的對象,放在固定區(qū),從而減少掃描垃圾時間及GC頻率,針對分類進行不同的垃圾回收算法。
- 在新生代中,每次垃圾收集中都會發(fā)現(xiàn)大批對象死去,只有少量存活,所有采用了復(fù)制算法
-
而老年代中因為對象存活率高,沒有額外空間對它進行分配擔保,就必須使用“標記--清理”或者“標記--整理“算法進行回收。
3.png
- 方法區(qū)
- 是各個線程共享的內(nèi)存區(qū)域
- 非堆內(nèi)存,用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量等。
- 在jdk1.8中廢除了方法區(qū),替代是元空間,它的本質(zhì)和方法區(qū)類似,但它并不在虛擬機中,而是在本地內(nèi)存中,默認情況下,元空間的大小僅受本地內(nèi)存的限制。
HotSpot虛擬機對象
- 虛擬機遇到一條new指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過,如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。
- 在為新生對象分配空間時,主要有兩種方式。指針碰撞和空閑列表
- 并發(fā)情況下的對象創(chuàng)建問題。為了保證操作的正確性,一種采取對分配內(nèi)存空間的動作進行同步處理-實際上虛擬機采用CAS配上失敗重試的方法保證更新操作的原子性;另一種是把內(nèi)存分配的動作按照線程劃分到不同的空間之中進行,即每個線程在java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(TLAB)。哪個線程要分配內(nèi)存,就在哪個線程的TLAb上分配,只有TLAB用完并分配新的TLAB時,才需要同步鎖定。
4.png
- 對象的內(nèi)存布局
在hotspot虛擬機中,主要分為3塊區(qū)域:對象頭、實例數(shù)據(jù)和對齊填充
對象頭主要分為兩部分信息,一部分是存儲對象自身的運行時數(shù)據(jù),另一部分是類型指針。

- 對象的訪問定位
java程序通過棧上的reference數(shù)據(jù)來操作堆上的具體對象。它是一個指向?qū)ο蟮囊?,但如何通過這個引用去定位、訪問堆中的具體對象,這是不確定的,取決于虛擬機的類型。當前主要有兩種方式:使用句柄和直接指針
- 使用句柄,會在java堆中劃分出一塊內(nèi)存來作為句柄池,在句柄中包含了對象實例數(shù)據(jù)和對象類型數(shù)據(jù)的地址信息。

- 使用直接指針訪問,在reference中存儲的是對象地址,在對象實例數(shù)據(jù)中存儲了到對象類型數(shù)據(jù)的指針。
7.png
垃圾回收
利用垃圾收集器對堆進行回收前,首先要確定的是哪些對象還存活著,哪些對象已經(jīng)“死去”。
起初典型的算法是引用計數(shù)算法 ,它為每個對象添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器就加1;當引用失效時,計數(shù)器就減1,當計數(shù)器為0時就是不可能再被使用了。但在某些情況下會出現(xiàn)一些錯誤,當對象間有相互循環(huán)引用時,會相互引用著對方,導致它們的引用計數(shù)都不為0。
可達性分析算法
該算法通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。

在java語言中,可作為GC Roots的對象有下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中的引用對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧JNI(即一般說的Native方法)引用的對象
一個對象真正死亡,至少要經(jīng)歷兩次標記記錄
第一次: 在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它會被第一次標記并進行篩選,篩選的條件是此對象是否有必要執(zhí)行的finalize()方法。當對象沒有覆蓋finalize()方法或者finalize()方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為“沒有必要執(zhí)行”。
第二次: 當這個對象被判定為有必要執(zhí)行finalize()方法 ,那么這個對象將會放置在一個F-Queue的隊列中,如果該對象重新與引用鏈上的任意一個對象建立關(guān)聯(lián)時,它就可以從“即將回收”的集合中移除。
垃圾回收算法
- 標記-清除算法
首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收所有所標記的對象

- 復(fù)制算法
它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的內(nèi)存用完時,就將還存活的對象復(fù)制到另外一塊上,然后再把已使用過的內(nèi)存空間一次清理掉。

- 標記-整理算法
當標記完待回收對象后,讓所有存活的對象都向一端移動,然后直接清理掉端邊界意外的內(nèi)存,
不是直接對可回收對象進行清理。
[站外圖片上傳中...(image-ff210b-1563195408545)] - 分代收集算法
新生代采用復(fù)制算法,在老年代采用“標記-清除”或者“標記-整理”算法。新生代分為Eden區(qū)和兩個相同大小的Survivor區(qū),
所有新創(chuàng)建的對象都分配在Eden區(qū)域中。當Eden區(qū)域滿后會觸發(fā)minor GC,將Eden區(qū)仍然存活的對象復(fù)制到其中一個Survivor區(qū)域中,另外一個Survivor區(qū)中的存活對象也復(fù)制到這個Survivor區(qū)域中,并始終保持一個Survivor區(qū)是空的。



