JVM運(yùn)行時數(shù)據(jù)區(qū)、類加載器和GC算法!!!
JVM簡介
JVM是Java虛擬機(jī),存在于JRE中,即Java運(yùn)行時環(huán)境,JVM是運(yùn)行在操作系統(tǒng)上的程序,而我們的Java程序是運(yùn)行在JRE(JVM)之上的,操作系統(tǒng)是運(yùn)行在硬件之上的。

我們所編寫的Java文件,首先會被編譯成class文件,然后通過類加載器進(jìn)行加載到JVM中,也就是我們的運(yùn)行時數(shù)據(jù)區(qū),核心有五部分組成,分別是,方法區(qū)、堆、虛擬機(jī)棧、本地方法棧、程序計數(shù)器。

類加載器
所謂類加載器,也就是將.class文件加載為Class模板,并可以通過new關(guān)鍵字創(chuàng)建實例對象,實例對象可以通過getClass方法獲取Class模板,Class模板可以通過getClassLoader方法獲取類加載器。

其中這里的類加載器有三種,分別是啟動類(根)加載器(BootStrap Class Loader)、擴(kuò)展加載器()ExtClassLoader、應(yīng)用程序加載器(AppClassLoader),根據(jù)雙親委派機(jī)制,當(dāng)加載一個對象的時候,會從應(yīng)用程序加載器往上找,找到擴(kuò)展類加載器,然后再往上找,找到根加載器,如果根加載器無法加載,那么就會看看擴(kuò)展類加載器是否可以加載,如果也不可以那么就去應(yīng)用程序加載器加載,如果還不可以,就會報錯。

我們一般自定義的類,都是被應(yīng)用程序加載器進(jìn)行加載,JDK自帶的類,一般都是通過根加載器進(jìn)行加載,下圖ext包下的類都是擴(kuò)展類加載器進(jìn)行加載的。

本地方法棧
本地方法棧主要是存儲被native關(guān)鍵字修飾的方法,該方法會調(diào)用本地方法接口(Java Native Interface JNI)本地方法接口會調(diào)用本地方法庫,其中這里面主要是一些C/C++程序。

程序計數(shù)器
程序計數(shù)器:程序計數(shù)器是線程私有的,每個線程都有一個程序計數(shù)器,是線程私有的,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。
方法區(qū)
方法區(qū):方法區(qū)是線程共享的,主要存儲靜態(tài)變量、常量、類型信息(構(gòu)造方法、接口的定義)和常量池。
虛擬機(jī)棧
當(dāng)我們的方法被調(diào)用的時候,就會進(jìn)棧,當(dāng)方法執(zhí)行結(jié)束的時候就會出棧,其中main方法是第一個進(jìn)棧的方法,當(dāng)然了,由于棧是先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),所以main方法是最后出棧的方法,即最后結(jié)束的方法。
當(dāng)我們的main方法調(diào)用其他方法的時候,其他方法也會進(jìn)棧,如果其他方法也調(diào)用了其他方法,那么還會進(jìn)棧那,如果我們有死循環(huán)(或其他不當(dāng)操作)進(jìn)棧,那么就會出現(xiàn)Stack Overflow即棧溢出。下面模擬一個棧溢出
package com.hzy;
public class Main {
public static void main(String[] args){
a();
}
public static void a() {
b();
}
public static void b() {
a();
}
}

棧中主要存放:八大基本數(shù)據(jù)類型、對象的引用、實例方法
堆
當(dāng)類加載器加載了類之后,會把類、方法、常量、變量放到堆中,并保存引用類型所指的真實對象。

當(dāng)某個對象使用完畢之后,會出棧,但是,所指向的對象會殘留在堆中,也就成為了垃圾。對于堆來說,又被細(xì)分為了三部分:新生區(qū)、老年區(qū)、元空間(JDK8以后永久區(qū)改名為元空間)。新生區(qū)又細(xì)分為:伊甸園區(qū)(Eden)、幸存0區(qū)、幸存1區(qū)。這樣分的目的也就是為了解決堆中的垃圾問題,GC垃圾回收機(jī)制就是,會對新生區(qū)進(jìn)行垃圾回收,沒有被回收的垃圾會進(jìn)入到幸存區(qū)(0區(qū)或1區(qū)),GC垃圾回收機(jī)制會對幸存區(qū)進(jìn)行回收,這種在新生區(qū)進(jìn)行垃圾回收的機(jī)制是輕量級,即輕GC,如果在幸存區(qū)也沒有被清理掉,那么就會進(jìn)入老年區(qū)(默認(rèn)是被清理了15次還沒死,就進(jìn)入老年區(qū)),對新生區(qū)和老年區(qū)都進(jìn)行垃圾回收的機(jī)制是重量級的,即重GC,會把所有的垃圾都清理一下,我們所聽說的垃圾回收機(jī)制,也就是對新生區(qū)和老年區(qū)進(jìn)行的回收。
如果我們的新生區(qū)和老年區(qū)都滿了,就會報一個Out Of Memory(OOM)內(nèi)存不足。下面模擬一個OOM
package com.hzy;
public class Main {
public static void main(String[] args){
String s = "hello";
while (true) {
s += s;
}
}
}


這里的元空間常駐在內(nèi)存中,主要是存放我們的Class對象,接口定義,常量池和Java運(yùn)行時的一些環(huán)境,這里不存在垃圾,虛擬機(jī)關(guān)閉時會被釋放內(nèi)存

這里需要注意一下,我們的方法區(qū)就是在元空間的,其中常量池是在方法區(qū)中的,很多時候,我們習(xí)慣把方法區(qū)單獨提出來,有時稱方法區(qū)為非堆。
GC算法
如何判斷該對象是垃圾?
引用計數(shù)法
用一個標(biāo)識位去計數(shù)該對象被引用的次數(shù),被引用一次該標(biāo)識位的值就加1,當(dāng)該標(biāo)識位的值為0的時候就認(rèn)為該對象是垃圾。
可達(dá)性分析法
從GC Roots往下搜索,如果某個對象不可達(dá)(也就是不在任何一棵樹上),就認(rèn)為該對象及與之相連的對象為垃圾(5,6,7為垃圾)。

GC主要是對新生區(qū)和老年區(qū)進(jìn)行回收
標(biāo)記-清除算法
標(biāo)記清除算法是最基礎(chǔ)的垃圾回收算法,主要包括兩步,標(biāo)記和清除,首先標(biāo)記處所有需要回收的對象,標(biāo)記之后,統(tǒng)一回收掉所有被標(biāo)記的對象,反過來也可以,標(biāo)記存活的對象,統(tǒng)一回收所有未標(biāo)記的對象。該算法進(jìn)行標(biāo)記清除后,會有大量不連續(xù)的內(nèi)存碎片。

標(biāo)記-復(fù)制算法
復(fù)制算法主要是對新生區(qū)進(jìn)行垃圾回收,其中新生區(qū)又包括:伊甸園區(qū),幸存0區(qū)和幸存1區(qū),其實這里的0區(qū)和1區(qū)又叫做from區(qū)和to區(qū),這里的from區(qū)和to區(qū)是動態(tài)的,當(dāng)進(jìn)行垃圾回收的時候,如果伊甸園區(qū)有殘余垃圾,from區(qū)也有殘余垃圾,那么會把這兩個區(qū)的垃圾都放到to區(qū),并把to區(qū)改名為from區(qū),目的是保證to區(qū)永遠(yuǎn)為空(誰空誰是to),方便把伊甸園區(qū)和from區(qū)的垃圾都放到to區(qū)。這樣一來就會有一個空閑的區(qū)域,導(dǎo)致空間浪費。

標(biāo)記-整理算法
即是對標(biāo)記清楚算法再次掃描,進(jìn)行空位填充,這樣一來,沒有了空余空間,但是多了一遍遍歷,更耗時(一般都是執(zhí)行多次標(biāo)記清除算法才執(zhí)行一次標(biāo)記壓縮算法)。

總結(jié)
對于GC垃圾回收算法適用場景:新生區(qū)用標(biāo)記-復(fù)制算法,老年區(qū)用標(biāo)記-整理算法
