JVM總結(jié)
JVM的位置
JVM在操作系統(tǒng)之上淤齐,它與硬件沒(méi)有直接的交互活玲!
JVM體系結(jié)構(gòu)圖
棧中絕不會(huì)有垃圾涣狗,否則會(huì)影響出棧,程序就死了舒憾!
類(lèi)加載器ClassLoader
一個(gè)類(lèi)加載到JVM的一個(gè)基本結(jié)構(gòu):
在如下幾種情況下镀钓,java虛擬機(jī)將結(jié)束生命周期:
- 執(zhí)行了System.exit()方法
- 程序正常執(zhí)行結(jié)束
- 程序執(zhí)行過(guò)程中遇到異常或者錯(cuò)誤而終止
- 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致java虛擬機(jī)終止
類(lèi)的加載镀迂、連接與初始化
在Java代碼中丁溅,類(lèi)的加載、連接與初始化過(guò)程都是在程序運(yùn)行期間完成的探遵。
加載:查找并加載類(lèi)的二進(jìn)制數(shù)據(jù)
-
連接:
- 驗(yàn)證:確保被加載的類(lèi)的正確性
- 準(zhǔn)備:為類(lèi)的靜態(tài)變量分配內(nèi)存骡苞,并將其初始化為默認(rèn)值
- 解析:把類(lèi)中的符號(hào)引用轉(zhuǎn)換為直接引用
在編譯的時(shí)候一個(gè)每個(gè)java類(lèi)都會(huì)被編譯成一個(gè)class文件吗铐,但在編譯的時(shí)候虛擬機(jī)并不知道所引用類(lèi)的地址,所以就用符號(hào)引用來(lái)代替,而在這個(gè)解析階段就是為了把這個(gè)符號(hào)引用轉(zhuǎn)化成為真正的地址的階段档悠。
初始化:為類(lèi)的靜態(tài)變量賦予正確的初始值
通過(guò)代碼來(lái)理解:
class Test{
public static int a = 1;
}
//我們程序中給定的是 public static int a = 1; //但是在加載過(guò)程中的步驟如下:
/*
1. 加載階段
編譯文件為 .class文件懂扼,然后通過(guò)類(lèi)加載烁落,加載到JVM
2. 連接階段
第一步(驗(yàn)證):確保Class類(lèi)文件沒(méi)問(wèn)題
第二步(準(zhǔn)備):先初始化為 a=0熄云。(因?yàn)槟鉯nt類(lèi)型的初始值為0)
第三步(解析):將引用轉(zhuǎn)換為直接引用
3. 初始化階段: 通過(guò)此解析階段,把1賦值為變量a
*/
類(lèi)的加載
類(lèi)的加載指的是將類(lèi)的.class文件中二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中诉稍,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)內(nèi)的方法區(qū)內(nèi)蝠嘉,然后再內(nèi)存中創(chuàng)建一個(gè)java.lang.Class
對(duì)象用來(lái)封裝類(lèi)在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
//對(duì)于靜態(tài)字段來(lái)說(shuō)杯巨,只有直接定義了該字段的類(lèi)才會(huì)被初始化;
//當(dāng)一個(gè)類(lèi)在初始化時(shí)蚤告,要求其父類(lèi)全部都已經(jīng)初始化完畢了;
//所有Java虛擬機(jī)實(shí)現(xiàn)必須在每個(gè)類(lèi)或者接口被Java程序“首次主動(dòng)使用”時(shí)才初始化他們
public class MyTest1 {
public static void main (String[] args){
System.out.println(MyChild1.str2);
}
}
class MyParent1{
public static String str = "hello world";
static {
System.out.println("MyParent1 static block ");
}
}
class MyChild1 extends MyParent1{
public static String str2 = "welcome";
static{
System.out.println("MyChild1 static block ");
}
}
// 輸出結(jié)果:
MyParent1 static block
MyChild1 static block
welcome
查看類(lèi)的加載信息,并打印出來(lái):
jvm 參數(shù)介紹:
-XX:+TraceClassLoading,用于追蹤類(lèi)的加載信息并打印出來(lái)服爷。
所有的參數(shù)都是:
-XX:+<option> 杜恰, 表示開(kāi)啟option選項(xiàng)
-XX:-<option> 获诈, 表示關(guān)閉option選項(xiàng)
-XX:+<option>=<value> 表示將option選項(xiàng)的值設(shè)置為value
常量池概念
問(wèn)題1
public class Test2 {
public static void main(String[] args) {
System.out.println(Parent2.str);
}
}
class Parent2 {
public static final String str = "hello world";
static {
System.out.println("parent2 靜態(tài)代碼塊");//不會(huì)輸出
}
}
只會(huì)輸出helloword,因?yàn)槌A吭诰幾g階段會(huì)存入到調(diào)用這個(gè)常量的方法所在的類(lèi)的常量池中心褐。本質(zhì)上舔涎,調(diào)用類(lèi)并沒(méi)有直接用到定義常量的類(lèi),因此并不會(huì)觸發(fā)定義常量的類(lèi)的初始化逗爹。
簡(jiǎn)而言之亡嫌,這里將常量存放到了Test2的常量池中,之后Test2與Parent2就沒(méi)有任何關(guān)系了掘而。但如果使用new Parent2()來(lái)調(diào)用挟冠,必將初始化Parent2,同時(shí)打印代碼塊里的內(nèi)容袍睡。
問(wèn)題2
public class Test3 {
public static void main(String[] args){
System.out.println(Parent3.str);
}
}
class Parent3{
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("parent3 靜態(tài)代碼塊");//會(huì)輸出
}
}
答案是會(huì)輸出靜態(tài)代碼塊里的內(nèi)容知染。和問(wèn)題1案例不同的是,此常量的值調(diào)用其他方法斑胜,只要初始化運(yùn)行的時(shí)候才會(huì)被確定控淡。而問(wèn)題1的常量的值在編譯時(shí)就確定了。
ClassLoader分類(lèi)
有兩種類(lèi)型的類(lèi)加載器
- Java虛擬機(jī)自帶的加載器
- 根類(lèi)加載器(BootStrap or BootClassLoader) sun.boot.class.path(加載系統(tǒng)的包伪窖,包含jdk核 心庫(kù)內(nèi)類(lèi))
- 擴(kuò)展類(lèi)加載器(Extension or ExtClassLoader) java.ext.dirs(加載擴(kuò)展jar包中的類(lèi))
- 系統(tǒng)(應(yīng)用)類(lèi)加載器(System or AppClassLoader) java.class.path(加載你編寫(xiě)的類(lèi)逸寓,編譯后的類(lèi))
- 用戶(hù)自定義的類(lèi)加載器
- Java.long.ClassLoader的子類(lèi)(繼承)居兆,用戶(hù)可以定制類(lèi)的加載方式
代碼測(cè)試:
public class ClassLoaderDemo01 {
public static void main(String[] args) {
Object object = new Object();
ClassLoaderDemo01 demo01 = new ClassLoaderDemo01();
System.out.println(object.getClass().getClassLoader());
System.out.println(demo01.getClass().getClassLoader());
System.out.println(demo01.getClass().getClassLoader().getParent());
System.out.println(demo01.getClass().getClassLoader().getParent().getParent
());
/*
結(jié)果:
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
**/
}
}
雙親委派機(jī)制
雙親委派機(jī)制的工作原理:一層一層的 讓父類(lèi)去加載覆山,最頂層父類(lèi)不能加載往下數(shù),依次類(lèi)推泥栖。
- 類(lèi)加載器收到類(lèi)加載的請(qǐng)求;
- 把這個(gè)請(qǐng)求委托給父加載器去完成簇宽,一直向上委托,直到啟動(dòng)類(lèi)加載器
- 啟動(dòng)器加載器檢查能不能加載(使用findClass()方法)吧享,能就加載(結(jié)束);否則拋出異常魏割,通知子加載器進(jìn)行加載
- 重復(fù)步驟三;
代碼測(cè)試:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println(1);
}
}
大家所熟知的String類(lèi),直接告訴大家钢颂,String默認(rèn)情況下是啟動(dòng)類(lèi)加載器進(jìn)行加載的钞它。假設(shè)我也自定義一個(gè)String 。現(xiàn)在你會(huì)發(fā)現(xiàn)自定義的String可以正常編譯殊鞭,但是永遠(yuǎn)無(wú)法被加載運(yùn)行遭垛。這是因?yàn)樯暾?qǐng)自定義String加載時(shí),總是啟動(dòng)類(lèi)加載器操灿,而不是自定義加載器锯仪,也不會(huì)是其他的加載器。雙親委派機(jī)制可以確保Java核心類(lèi)庫(kù)所提供的類(lèi)趾盐,不會(huì)被自定義的類(lèi)所替代庶喜。
Native方法
凡是帶了native關(guān)鍵字的小腊,說(shuō)明java的作用范圍達(dá)不到,去調(diào)用底層C語(yǔ)言的庫(kù)!
JNI:Java Native Interface (Java本地方法接口)
凡是帶了native關(guān)鍵字的方法就會(huì)進(jìn)入本地方法棧--->Native Method Stack
作用:融合不同的編程語(yǔ)言為Java所用久窟,它的初衷是融合C/C++程序秩冈,調(diào)用C、C++的程序斥扛,于是就在內(nèi)存中專(zhuān)門(mén)開(kāi)辟了一塊區(qū)域處理標(biāo)記為native的代碼漩仙,它的具體做法是在Native Method Stack 中登記native方法,在(Execution Engine)執(zhí)行引擎執(zhí)行的時(shí)候加載Native Libraies犹赖。
程序計(jì)數(shù)器
每個(gè)線程都有一個(gè)程序計(jì)數(shù)器队他,是線程私有的。
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間峻村,它的作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器麸折。在虛擬機(jī)的概念模型里字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支粘昨、循環(huán)垢啼、跳轉(zhuǎn)、異常處理张肾、線程恢復(fù)等基礎(chǔ)功能都需要依賴(lài)這個(gè)計(jì)數(shù)器來(lái)完成芭析。保證程序執(zhí)行順序。是一個(gè)非常小的內(nèi)存空間吞瞪,幾乎可以忽略不計(jì)馁启。
代碼案例:
public class Calc {
public int calc(){
int a = 100;
int b = 200;
int c = 300;
return ( a + b ) * c;
}
}
反編譯: Javap -c xx.class
- ldc:表示將int、float或是String類(lèi)型的常量值從常量池中推送至棧頂
- bipush:表示將單字節(jié)(-128~127)的常量值推送至棧頂芍秆。
- sipush:表示將短整型(-32767~32768)的常量值推送至棧頂惯疙。
- istore_1:將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表
- iadd:加
- imul:乘
圖中使用紅框框起來(lái)的就是字節(jié)碼指令的偏移地址,偏移地址對(duì)應(yīng)的bipush 等等是jvm中的操作指令, 這是入棧指令妖啥。 當(dāng)執(zhí)行到方法calc()時(shí)在當(dāng)前的線程中會(huì)創(chuàng)建相應(yīng)的程序計(jì)數(shù)器霉颠,在計(jì)數(shù)器中為存放執(zhí) 行地址 (紅框中的)0 2 3...等等
方法區(qū)
靜態(tài)變量、常量荆虱、類(lèi)信息(構(gòu)造方法——蒿偎、接口定義)、運(yùn)行時(shí)的常量池存在方法區(qū)中怀读,但是實(shí)例變量存在堆內(nèi)存中诉位,和方法無(wú)關(guān)。(static,final,Class,常量池)
棧
什么是棧
- 棧也叫棧內(nèi)存愿吹,主管Java程序的運(yùn)行不从,是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命期犁跪,線程 結(jié)束棧內(nèi)存也就釋放椿息。
- 對(duì)于棧來(lái)說(shuō)不存在垃圾回收問(wèn)題歹袁,只要線程一旦結(jié)束,該棧就Over寝优,生命周期和線程一致条舔,是線程私有的。
棧的運(yùn)行原理
Java棧的組成元素是棧幀乏矾,棧幀是一種用于幫助虛擬機(jī)執(zhí)行方法調(diào)用與方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)孟抗。他是獨(dú)立于線程的,一個(gè)線程有自己的一個(gè)棧幀钻心。封裝了方法的局部變量表凄硼、動(dòng)態(tài)鏈接信息、方法的返回地址以及操作數(shù)棧等信息捷沸。
第一個(gè)方法從調(diào)用開(kāi)始到執(zhí)行完成摊沉,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。
堆
Java堆是java虛擬機(jī)所管理內(nèi)存中最大的一塊內(nèi)存空間痒给,處于物理上不連續(xù)的內(nèi)存空間说墨,只要邏輯連續(xù)即可,主要用于存放各種類(lèi)的實(shí)例對(duì)象苍柏。該區(qū)域被所有線程共享尼斧,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,用來(lái)存放對(duì)象的實(shí)例试吁,幾乎所有的對(duì)象以及數(shù)組都在這里分配內(nèi)存(棧上分配棺棵、標(biāo)量替換優(yōu)化技術(shù)的例外)。
在 Java 中潘悼,堆被劃分成兩個(gè)不同的區(qū)域:新生代 ( Young )律秃、老年代 ( Old )爬橡。新生代 ( Young ) 又被劃分為三個(gè)區(qū)域:Eden治唤、From Survivor(S0)、To Survivor(S1)糙申。如圖所示:
新生代
新生代是類(lèi)誕生宾添,成長(zhǎng),消亡的區(qū)域柜裸,一個(gè)類(lèi)在這里產(chǎn)生缕陕,應(yīng)用,最后被垃圾回收器收集疙挺,結(jié)束生命扛邑。
新生代含有兩部分:伊甸區(qū)(Eden Space)和幸存者區(qū)(Survivor Space),所有的類(lèi)都是在伊甸區(qū)被new出來(lái)的铐然,幸存區(qū)有兩個(gè):0區(qū)和1區(qū)蔬崩,當(dāng)伊甸園的空間用完時(shí)恶座,程序又需要?jiǎng)?chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC)沥阳。將伊甸園中的剩余對(duì)象移動(dòng)到幸存0區(qū)跨琳,若幸存0區(qū)也滿(mǎn)了,再對(duì)該區(qū)進(jìn)行垃圾回收桐罕,然后移動(dòng)到1區(qū)脉让,那如果1區(qū)也滿(mǎn)了呢?(這里幸存0區(qū)和1區(qū)是一個(gè)互相交替的過(guò)程)再移動(dòng)到老年代,若老年代也滿(mǎn)了功炮,那么這個(gè)時(shí)候?qū)a(chǎn)生MajorGC(Full GC)溅潜,進(jìn)行老年區(qū)的內(nèi)存清理,若老年代執(zhí)行了Full GC后發(fā)現(xiàn)依然無(wú)法進(jìn)行對(duì)象的保存薪伏,就會(huì)產(chǎn)生OOM異常 “OutOfMemoryError ”伟恶。
如果出現(xiàn) java.lang.OutOfMemoryError:java heap space異常,說(shuō)明Java虛擬機(jī)的堆內(nèi)存不夠毅该,原因如下:
- Java虛擬機(jī)的堆內(nèi)存設(shè)置不夠博秫,可以通過(guò)參數(shù) -Xms(初始值大小),-Xmx(最大大小)來(lái)調(diào)整眶掌。
- 代碼中創(chuàng)建了大量大對(duì)象挡育,并且長(zhǎng)時(shí)間不能被垃圾收集器收集(存在被引用)或者死循環(huán)
永久代
永久存儲(chǔ)區(qū)是一個(gè)常駐內(nèi)存區(qū)域,用于存放JDK自身所攜帶的Class朴爬,Interface的元數(shù)據(jù)即寒,也就是說(shuō)它存儲(chǔ)的是運(yùn)行環(huán)境必須的類(lèi)信息,被裝載進(jìn)此區(qū)域的數(shù)據(jù)是不會(huì)被垃圾回收器回收掉的召噩,關(guān)閉JVM才會(huì)釋 放此區(qū)域所占用的內(nèi)存母赵。
如果出現(xiàn)java.lang.OutOfMemoryError:PermGen space,說(shuō)明是Java虛擬機(jī)對(duì)永久代Perm內(nèi)存設(shè)置不夠具滴。一般出現(xiàn)這種情況凹嘲,都是程序啟動(dòng)需要加載大量的第三方j(luò)ar包,例如:在一個(gè)Tomcat下部署了太多的應(yīng)用构韵≈懿洌或者大量動(dòng)態(tài)反射生成的類(lèi)不斷被加載,最終導(dǎo)致Perm區(qū)被占滿(mǎn)疲恢。
注意:
Jdk1.6之前 : 有永久代凶朗,常量池1.6在方法區(qū)
Jdk1.7. : 有永久代,但是已經(jīng)逐步 “去永久代”显拳,常量池1.7在堆
Jdk1.8及之后: 無(wú)永久代棚愤,常量池1.8在元空間(1.8之前的永久代)
堆內(nèi)存調(diào)優(yōu)
-Xms :設(shè)置初始分配大小,默認(rèn)為物理內(nèi)存的“1/64”
-Xmx :最大分配內(nèi)存杂数,默認(rèn)為物理內(nèi)存的“1/4”
-XX:+PrintGCDetails :輸出詳細(xì)的GC處理日志
Dump內(nèi)存快照
在idea中也有這么一個(gè)插件宛畦, 就是JProfiler矛绘,一款性能瓶頸分析工具! 需要在idea里下載拆件,并在Settings–Tools–JProflier–JProflier executable中配置JProfile安裝可執(zhí)行文件
作用:
- 分析Dump文件刃永,快速定位內(nèi)存泄漏;
- 獲得堆中對(duì)象的統(tǒng)計(jì)數(shù)據(jù)
- 獲得對(duì)象相互引用的關(guān)系
- 采用樹(shù)形展現(xiàn)對(duì)象間相互引用的情況
代碼測(cè)試:
public class Demo03 {
byte[] byteArray = new byte[1*1024*1024]; //1M = 1024K
public static void main(String[] args) {
ArrayList<Demo03> list = new ArrayList<>();
int count = 0;
try {
while (true){
list.add(new Demo03());
count = count + 1;
}
}catch (Error e){
System.out.println("count:"+count);
e.printStackTrace();
}
}
}
vm參數(shù) :-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
運(yùn)行結(jié)果
找到文件货矮,并用jprofiler打開(kāi),即可進(jìn)行分析
GC垃圾回收
GC作用域在于方法區(qū)和堆
JVM在進(jìn)行GC時(shí)斯够,并非每次都對(duì)上面三個(gè)內(nèi)存區(qū)域一起回收的囚玫,大部分時(shí)候回收的都是指新生代
因此GC按照回收的區(qū)域又分了兩種類(lèi)型,一種是普通的GC(minor GC)读规,一種是全局GC (major GC or Full GC)
普通GC:只針對(duì)新生代區(qū)域的GC
全局GC:針對(duì)老年代的GC抓督,偶爾伴隨對(duì)新生代的GC以及對(duì)永久代的GC
判斷垃圾可以回收的算法
引用計(jì)數(shù)法
基本概念:
引用計(jì)數(shù)是垃圾收集器中的早期策略。在這種方法中束亏,堆中每個(gè)對(duì)象實(shí)例都有一個(gè)引用計(jì)數(shù)铃在。當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),就將該對(duì)象實(shí)例分配給一個(gè)變量碍遍,該變量計(jì)數(shù)設(shè)置為 1定铜。當(dāng)任何其它變量被賦值為這個(gè)對(duì)象的引用時(shí),計(jì)數(shù)加1(a = b怕敬,則 b 引用的對(duì)象實(shí)例的計(jì)數(shù)器加 1)揣炕,但當(dāng)一個(gè)對(duì)象實(shí)例的某個(gè)引用超過(guò)了生命周期或者被設(shè)置為一個(gè)新值時(shí),對(duì)象實(shí)例的引用計(jì)數(shù)器減 1东跪。任何引用計(jì)數(shù)器為 0 的對(duì)象實(shí)例可以被當(dāng)作垃圾收集畸陡。當(dāng)一個(gè)對(duì)象實(shí)例被垃圾收集時(shí),它引用的任何對(duì)象實(shí)例的引用計(jì)數(shù)器減 1虽填。
優(yōu)點(diǎn):
引用計(jì)數(shù)收集器可以很快的執(zhí)行丁恭,交織在程序運(yùn)行中。對(duì)程序需要不被長(zhǎng)時(shí)間打斷的實(shí)時(shí)環(huán)境比較有利斋日。
缺點(diǎn):
無(wú)法檢測(cè)出循環(huán)引用牲览。如父對(duì)象有一個(gè)對(duì)子對(duì)象的引用,子對(duì)象反過(guò)來(lái)引用父對(duì)象桑驱。這樣竭恬,他們的引用計(jì)數(shù)永遠(yuǎn)不可能為 0。
可達(dá)性分析算法
基本概念:
可達(dá)性分析算法是從離散數(shù)學(xué)中的圖論引入的熬的,程序把所有的引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn) GC ROOT 開(kāi)始赊级,尋找對(duì)應(yīng)的引用節(jié)點(diǎn)押框,找到這個(gè)節(jié)點(diǎn)以后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn)理逊,當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后橡伞,剩余的節(jié)點(diǎn)則被認(rèn)為是沒(méi)有被引用到的節(jié)點(diǎn)盒揉,即無(wú)用的節(jié)點(diǎn),無(wú)用的節(jié)點(diǎn)將會(huì)被判定為是可回收的對(duì)象兑徘。
在Java語(yǔ)言中刚盈,可作為GC Roots的對(duì)象包括下面幾種:
- 虛擬機(jī)棧中引用的對(duì)象(棧幀中的本地變量表)
- 方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中 JNI(Native方法)引用的對(duì)象
常用的垃圾收集算法
標(biāo)記清除算法(Mark-Sweep)
標(biāo)記-清除算法采用從根集合(GC Roots)進(jìn)行掃描,對(duì)存活的對(duì)象進(jìn)行標(biāo)記挂脑,標(biāo)記完畢后藕漱,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象,進(jìn)行回收崭闲。標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng)肋联,只需對(duì)不存活的對(duì)象進(jìn)行處理,在存活對(duì)象比較多的情況下極為高效刁俭,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象橄仍,因此會(huì)造成內(nèi)存碎片。
缺點(diǎn):
這個(gè)算法需要暫停整個(gè)應(yīng)用牍戚,會(huì)產(chǎn)生內(nèi)存碎片侮繁。所謂的內(nèi)存碎片就是這塊哈希地址不可用了,出現(xiàn)零散的內(nèi)存碎片如孝。碎片最直接的問(wèn)題就是會(huì)導(dǎo)致無(wú)法分配大塊的內(nèi)存空間鼎天,以及程序運(yùn)行效率降低。
用通俗的話解釋一下 標(biāo)記/清除算法暑竟,就是當(dāng)程序運(yùn)行期間斋射,若可以使用的內(nèi)存被耗盡的時(shí)候,GC線程 就會(huì)被觸發(fā)并將程序暫停但荤,隨后將依舊存活的對(duì)象標(biāo)記一遍罗岖,最終再將堆中所有沒(méi)被標(biāo)記的對(duì)象全部清 除掉,接下來(lái)便讓程序恢復(fù)運(yùn)行腹躁。
復(fù)制算法(Copying)
當(dāng)對(duì)象在Eden(包括一個(gè)Survivor區(qū)域桑包,這里假設(shè)是From區(qū)域)出生后,在經(jīng)過(guò)一次Minor GC后纺非,如 果對(duì)象還存活哑了,并且能夠被另外一塊Survivor區(qū)域所容納 (上面已經(jīng)假設(shè)為from區(qū)域,這里應(yīng)為to區(qū)域烧颖,即to區(qū)域有足夠的內(nèi)存空間來(lái)存儲(chǔ)Eden 和 From 區(qū)域中存活的對(duì)象)弱左,則使用復(fù)制算法將這些仍然 還活著的對(duì)象復(fù)制到另外一塊Survivor區(qū)域(即 to 區(qū)域)中,然后清理所使用過(guò)的Eden 以及Survivor 區(qū)域(即form區(qū)域)炕淮,并且將這些對(duì)象的年齡設(shè)置為1拆火,以后對(duì)象在Survivor區(qū),每熬過(guò)一次Minor GC,就將這個(gè)對(duì)象的年齡 + 1们镜,當(dāng)這個(gè)對(duì)象的年齡達(dá)到某一個(gè)值的時(shí)候(默認(rèn)是15歲币叹,通過(guò)-XX:MaxTenuringThreshold
設(shè)定參數(shù))這些對(duì)象就會(huì)成為老年代。
如何判斷哪個(gè)是幸存區(qū)中的to區(qū)呢? 一句話:誰(shuí)空誰(shuí)是to
標(biāo)記壓縮(Mark-Compact)
在整理壓縮階段模狭,不再對(duì)標(biāo)記的對(duì)象作回收颈抚,而是通過(guò)所有存活對(duì)象都像一端移動(dòng),然后直接清除邊界 以外的內(nèi)存嚼鹉》泛海可以看到,標(biāo)記的存活對(duì)象將會(huì)被整理反砌,按照內(nèi)存地址依次排列雾鬼,而未被標(biāo)記的內(nèi)存會(huì)被 清理掉,如此一來(lái)宴树,當(dāng)我們需要給新對(duì)象分配內(nèi)存時(shí)策菜,JVM只需要持有一個(gè)內(nèi)存的起始地址即可,這比 維護(hù)一個(gè)空閑列表顯然少了許多開(kāi)銷(xiāo)酒贬。
分代收集算法
年輕代(Young Generation)的回收算法 (主要以 Copying 為主)
- 所有新生成的對(duì)象首先都是放在年輕代的又憨。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對(duì)象。
- 新生代發(fā)生的 GC 也叫做 Minor GC锭吨,MinorGC 發(fā)生頻率比較高(不一定等 Eden 區(qū)滿(mǎn)了才觸發(fā))
年老代(Old Generation)的回收算法(主要以 Mark-Compact 為主)
- 在年輕代中經(jīng)歷了 N 次垃圾回收后仍然存活的對(duì)象蠢莺,就會(huì)被放到年老代中。因此零如,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象
- 內(nèi)存比新生代也大很多(大概比例是1 : 2)躏将,當(dāng)老年代內(nèi)存滿(mǎn)時(shí)觸發(fā) Major GC 即 Full GC,F(xiàn)ull GC 發(fā)生頻率比較低考蕾,老年代對(duì)象存活時(shí)間比較長(zhǎng)祸憋,存活率標(biāo)記高