一庞萍、虛擬機(jī)
?同樣的java代碼在不同平臺(tái)生成的機(jī)器碼肯定是不一樣的,因?yàn)椴煌牟僮飨到y(tǒng)底層的硬件指令集是不同的枚赡。
同一個(gè)java代碼在windows上生成的機(jī)器碼可能是0101.......制妄,在linux上生成的可能是1100......坠狡,那么這是怎么實(shí)現(xiàn)的呢?
不知道同學(xué)們還記不記得口猜,在下載jdk的時(shí)候负溪,我們?cè)趏racle官網(wǎng),基于不同的操作系統(tǒng)或者位數(shù)版本要下載不同的jdk版本济炎,也就是說(shuō)針對(duì)不同的操作系統(tǒng)川抡,jdk虛擬機(jī)有不同的實(shí)現(xiàn)。
那么虛擬機(jī)又是什么東西呢须尚,如圖是從軟件層面屏蔽不同操作系統(tǒng)在底層硬件與指令上的區(qū)別崖堤,也就是跨平臺(tái)的由來(lái)侍咱。
說(shuō)到這里同學(xué)們可能還是有點(diǎn)不太明白,說(shuō)的還是太宏觀了密幔,那我們來(lái)了解下java虛擬機(jī)的組成楔脯。
二、虛擬機(jī)組成
?1.棧
我們先講一下其中的一塊內(nèi)存區(qū)域棧胯甩,大家都知道棧是存儲(chǔ)局部變量的昧廷,也是線程獨(dú)有的區(qū)域,也就是每一個(gè)線程都會(huì)有自己獨(dú)立的棧區(qū)域偎箫。
public class Math {
? ? public static int initData = 666;
? ? public static User user = new User();
? ? public int compute() {
? ? ? ? int a = 1;
? ? ? ? int b = 2;
? ? ? ? int c = (a+b) * 10;
? ? ? ? return c;
? ? }
? ? public static void main(String[] args) {
? ? ? ? Math math = new Math();
? ? ? ? math.compute();
? ? ? ? System.out.println("test");
? ? }
}
說(shuō)起棧大家都不會(huì)陌生麸粮,數(shù)據(jù)結(jié)構(gòu)中就有學(xué),這里線程棧中存儲(chǔ)數(shù)據(jù)的部分使用的就是棧镜廉,先進(jìn)后出弄诲。
大家都知道每個(gè)方法都有自己的局部變量,比如上圖中main方法中的math娇唯,compute方法中的a b c齐遵,那么java虛擬機(jī)為了區(qū)分不同方法中局部變量作用域范圍的內(nèi)存區(qū)域,每個(gè)方法在運(yùn)行的時(shí)候都會(huì)分配一塊獨(dú)立的棧幀內(nèi)存區(qū)域塔插,我們?cè)囍瓷蠄D中的程序來(lái)簡(jiǎn)單畫(huà)一下代碼執(zhí)行的內(nèi)存活動(dòng)梗摇。
執(zhí)行main方法中的第一行代碼是,棧中會(huì)分配main()方法的棧幀想许,并存儲(chǔ)math局部變量,伶授,接著執(zhí)行compute()方法,那么棧又會(huì)分配compute()的棧幀區(qū)域流纹。
這里的棧存儲(chǔ)數(shù)據(jù)的方式和數(shù)據(jù)結(jié)構(gòu)中學(xué)習(xí)的棧是一樣的糜烹,先進(jìn)后出。當(dāng)compute()方法執(zhí)行完之后漱凝,就會(huì)出棧被釋放疮蹦,也就符合先進(jìn)后出的特點(diǎn),后調(diào)用的方法先出棧茸炒。
棧幀
那么棧幀內(nèi)部其實(shí)不只是存放局部變量的愕乎,它還有一些別的東西,主要由四個(gè)部分組成壁公。
那么要講這個(gè)就會(huì)涉及到更底層的原理--字節(jié)碼感论。我們先看下我們上面代碼的字節(jié)碼文件。
看著就是一個(gè)16字節(jié)的文件紊册,看著像亂碼比肄,其實(shí)每個(gè)都是有對(duì)應(yīng)的含義的,oracle官方是有專門(mén)的jvm字節(jié)碼指令手冊(cè)來(lái)查詢每組指令對(duì)應(yīng)的含義的。那我們研究的薪前,當(dāng)然不是這個(gè)润努。
jdk有自帶一個(gè)javap的命令,可以將上述class文件生成一種更可讀的字節(jié)碼文件示括。
?我們使用javap -c命令將class文件反編譯并輸出到TXT文件中铺浇。
Compiled from "Math.java"
public class com.example.demo.test1.Math {
? public static int initData;
? public static com.example.demo.bean.User user;
? public com.example.demo.test1.Math();
? ? Code:
? ? ? 0: aload_0
? ? ? 1: invokespecial #1? ? ? ? ? ? ? ? ? // Method java/lang/Object."<init>":()V
? ? ? 4: return
? public int compute();
? ? Code:
? ? ? 0: iconst_1
? ? ? 1: istore_1
? ? ? 2: iconst_2
? ? ? 3: istore_2
? ? ? 4: iload_1
? ? ? 5: iload_2
? ? ? 6: iadd
? ? ? 7: bipush? ? ? ? 10
? ? ? 9: imul
? ? ? 10: istore_3
? ? ? 11: iload_3
? ? ? 12: ireturn
? public static void main(java.lang.String[]);
? ? Code:
? ? ? 0: new? ? ? ? ? #2? ? ? ? ? ? ? ? ? // class com/example/demo/test1/Math
? ? ? 3: dup
? ? ? 4: invokespecial #3? ? ? ? ? ? ? ? ? // Method "<init>":()V
? ? ? 7: astore_1
? ? ? 8: aload_1
? ? ? 9: invokevirtual #4? ? ? ? ? ? ? ? ? // Method compute:()I
? ? ? 12: pop
? ? ? 13: getstatic? ? #5? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? 16: ldc? ? ? ? ? #6? ? ? ? ? ? ? ? ? // String test
? ? ? 18: invokevirtual #7? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V
? ? ? 21: return
? static {};
? ? Code:
? ? ? 0: sipush? ? ? ? 666
? ? ? 3: putstatic? ? #8? ? ? ? ? ? ? ? ? // Field initData:I
? ? ? 6: new? ? ? ? ? #9? ? ? ? ? ? ? ? ? // class com/example/demo/bean/User
? ? ? 9: dup
? ? ? 10: invokespecial #10? ? ? ? ? ? ? ? // Method com/example/demo/bean/User."<init>":()V
? ? ? 13: putstatic? ? #11? ? ? ? ? ? ? ? // Field user:Lcom/example/demo/bean/User;
? ? ? 16: return
}
此時(shí)的jvm指令碼就清晰很多了,大體結(jié)構(gòu)是可以看懂的垛膝,類鳍侣、靜態(tài)變量、構(gòu)造方法吼拥、compute()方法倚聚、main()方法。
其中方法中的指令還是有點(diǎn)懵凿可,我們舉compute()方法來(lái)看一下:
Code:
? ? ? 0: iconst_1
? ? ? 1: istore_1
? ? ? 2: iconst_2
? ? ? 3: istore_2
? ? ? 4: iload_1
? ? ? 5: iload_2
? ? ? 6: iadd
? ? ? 7: bipush? ? ? ? 10
? ? ? 9: imul
? ? ? 10: istore_3
? ? ? 11: iload_3
? ? ? 12: ireturn
這幾行代碼就是對(duì)應(yīng)的我們代碼中compute()方法中的四行代碼惑折。大家都知道越底層的代碼,代碼實(shí)現(xiàn)的行數(shù)越多枯跑,因?yàn)樗麜?huì)包含一些java代碼在運(yùn)行時(shí)底層隱藏的一些細(xì)節(jié)原理惨驶。
那么一樣的,這個(gè)jvm指令官方也是有手冊(cè)可以查閱的敛助,網(wǎng)上也有很多翻譯版本粗卜,大家如果想了解可自行百度。
這里我只講解本博文設(shè)計(jì)代碼中的部分指令含義:
0. 將int類型常量1壓入操作數(shù)棧
0: iconst_1? ?
這一步很簡(jiǎn)單纳击,就是將1壓入操作數(shù)棧?
?1.??將int類型值存入局部變量1?
1: istore_1? ?
局部變量1续扔,在我們代碼中也就是第一個(gè)局部變量a,先給a在局部變量表中分配內(nèi)存焕数,然后將int類型的值纱昧,也就是目前唯一的一個(gè)1存入局部變量a
?2.?將int類型常量2壓入操作數(shù)棧
2: iconst_2
3.??將int類型值存入局部變量2?
3: istore_2
這兩行代碼就和前兩行類似了。
?4. 從局部變量1中裝載int類型值
4: iload_1
5.?從局部變量2中裝載int類型值?
5: iload_2
這兩個(gè)代碼是將局部變量1和2百匆,也就是a和b的值裝載到操作數(shù)棧中
?6. 執(zhí)行int類型的加法
6: iadd
iadd指令一執(zhí)行砌些,會(huì)將操作數(shù)棧中的1和2依次從棧底彈出并相加呜投,然后把運(yùn)算結(jié)果3在壓入操作數(shù)棧底加匈。
?7. 將一個(gè)8位帶符號(hào)整數(shù)壓入棧
7: bipush? ? ? ? 10
?這個(gè)指令就是將10壓入棧
?8. 執(zhí)行int類型的乘法
9: imul
這里就類似上面的加法了,將3和10彈出棧仑荐,把結(jié)果30壓入棧
?9.?將將int類型值存入局部變量3?
10: istore_3
這里大家就不陌生了吧雕拼,和第二步第三步是一樣的,將30存入局部變量3粘招,也就是c
?10.?從局部變量3中裝載int類型值
11: iload_3
這個(gè)前面也說(shuō)了
11. 返回int類型值
12: ireturn
這個(gè)就不用多說(shuō)了啥寇,就是將操作數(shù)棧中的30返回
到這里就把我們compute()方法講解完了,講完有沒(méi)有對(duì)局部變量表和操作數(shù)棧的理解有所加深呢?說(shuō)白了賦值號(hào)=后面的就是操作數(shù)辑甜,在這些操作數(shù)進(jìn)行賦值衰絮,運(yùn)算的時(shí)候需要內(nèi)存存放,那就是存放在操作數(shù)棧中磷醋,作為臨時(shí)存放操作數(shù)的一小塊內(nèi)存區(qū)域猫牡。
接下來(lái)我們?cè)僬f(shuō)說(shuō)方法出口。
方法出口說(shuō)白了不就是方法執(zhí)行完了之后要出到哪里邓线,那么我們知道上面compute()方法執(zhí)行完之后應(yīng)該回到main()方法第三行那么當(dāng)main()方法調(diào)用compute()的時(shí)候淌友,compute()棧幀中的方法出口就存儲(chǔ)了當(dāng)前要回到的位置,那么當(dāng)compute()方法執(zhí)行完之后骇陈,會(huì)根據(jù)方法出口中存儲(chǔ)的相關(guān)信息回到main()方法的相應(yīng)位置震庭。
那么main()方同樣有自己的棧幀,在這里有些不同的地方我們講一下你雌。
我們上面已經(jīng)知道局部變量會(huì)存放在棧幀中的局部變量表中器联,那么main()方法中的math會(huì)存入其中,但是這里的math是一個(gè)對(duì)象婿崭,我們知道new出來(lái)的對(duì)象是存放在堆中的
那么這個(gè)math變量和堆中的對(duì)象有什么聯(lián)系呢主籍?是同一個(gè)概念么?
當(dāng)然不是的逛球,局部變量表中的math存儲(chǔ)的是堆中那個(gè)math對(duì)象在堆中的內(nèi)存地址
2.程序計(jì)數(shù)器
程序計(jì)數(shù)器也是線程私有的區(qū)域千元,每個(gè)線程都會(huì)分配程序計(jì)數(shù)器的內(nèi)存,是用來(lái)存放當(dāng)前線程正在運(yùn)行或者即將要運(yùn)行的jvm指令碼對(duì)應(yīng)的地址颤绕,或者說(shuō)行號(hào)位置幸海。
上述代碼中每個(gè)指令碼前面都有一個(gè)行號(hào),你就可以把它看作當(dāng)前線程執(zhí)行到某一行代碼位置的一個(gè)標(biāo)識(shí)奥务,這個(gè)值就是程序計(jì)數(shù)器的值物独。
那么jvm虛擬機(jī)為什么要設(shè)置程序計(jì)數(shù)器這個(gè)結(jié)構(gòu)呢?就是為了多線程的出現(xiàn)氯葬,多線程之間的切換挡篓,當(dāng)一個(gè)程序被掛起的時(shí)候,總是要恢復(fù)的帚称,那么恢復(fù)到哪個(gè)位置呢官研,總不能又重新開(kāi)始執(zhí)行吧,那么程序計(jì)數(shù)器就解決了這個(gè)問(wèn)題闯睹。
3.方法區(qū)
在jdk1.8之前戏羽,有一個(gè)名稱叫做持久帶/永久代,很多同學(xué)應(yīng)該聽(tīng)過(guò)楼吃,在jdk1.8之后始花,oracle官方改名為元空間妄讯。存放常量、靜態(tài)變量酷宵、類元信息亥贸。
public static int initData = 666;
這個(gè)initData就是靜態(tài)變量,毋庸置疑是存放在方法區(qū)的
public static User user = new User();
那么這個(gè)user就有點(diǎn)不一樣了浇垦,user變量放在方法區(qū)砌函,new的User是存放在堆中的
到這里我們就能意識(shí)到棧,堆溜族,方法區(qū)之間都是有聯(lián)系的讹俊。
棧中的局部變量,方法區(qū)中的靜態(tài)變量煌抒,如果是對(duì)象類型的話都會(huì)指向堆中new出來(lái)中的對(duì)象仍劈,那么紅色的聯(lián)系代表什么呢?我們先來(lái)了解一下對(duì)象寡壮。
對(duì)象組成
你對(duì)對(duì)象的了解有多少呢贩疙,天天用對(duì)象,你是否知道對(duì)象在虛擬機(jī)中的存儲(chǔ)結(jié)構(gòu)呢况既?
對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:對(duì)象頭(Header)这溅、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。下圖是普通對(duì)象實(shí)例與數(shù)組對(duì)象實(shí)例的數(shù)據(jù)結(jié)構(gòu):
對(duì)象頭
HotSpot虛擬機(jī)的對(duì)象頭包括兩部分信息:
Mark Word
? ? ? 第一部分markword,用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)棒仍,如哈希碼(HashCode)悲靴、GC分代年齡、鎖狀態(tài)標(biāo)志莫其、線程持有的鎖癞尚、偏向線程ID、偏向時(shí)間戳等乱陡,這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)(未開(kāi)啟壓縮指針)中分別為32bit和64bit浇揩,官方稱它為“MarkWord”。
Klass Pointer
? ? ? ? 對(duì)象頭的另外一部分是klass類型指針憨颠,即對(duì)象指向它的類元數(shù)據(jù)的指針胳徽,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例.
數(shù)組長(zhǎng)度(只有數(shù)組對(duì)象有)
? ? ? ?如果對(duì)象是一個(gè)數(shù)組, 那在對(duì)象頭中還必須有一塊數(shù)據(jù)用于記錄數(shù)組長(zhǎng)度.
實(shí)例數(shù)據(jù)
? ? ? ? 實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容爽彤。無(wú)論是從父類繼承下來(lái)的养盗,還是在子類中定義的,都需要記錄起來(lái)淫茵。
對(duì)齊填充
? ? ? ? 第三部分對(duì)齊填充并不是必然存在的爪瓜,也沒(méi)有特別的含義,它僅僅起著占位符的作用匙瘪。由于HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍铆铆,換句話說(shuō),就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍丹喻。而對(duì)象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍)薄货,因此,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí)碍论,就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全谅猾。
其中的klass類型指針就是那條紅色的聯(lián)系,那是怎么聯(lián)系的呢鳍悠?
new Thread().start();
類加載其實(shí)最終是以類元信息的形式存儲(chǔ)在方法區(qū)中的税娜,math和math2都是由同一個(gè)類new出來(lái)的,當(dāng)對(duì)象被new時(shí)藏研,都會(huì)在對(duì)象頭中存儲(chǔ)一個(gè)指向類元信息的指針敬矩,這就是Klass? Pointer.
到這里我們就講解了棧,程序計(jì)數(shù)器和方法區(qū)蠢挡,下面我們簡(jiǎn)單介紹一下本地方法區(qū)弧岳,最后再終點(diǎn)講解堆。
4.本地方法棧
實(shí)際上現(xiàn)在本地方法棧已經(jīng)用的比較少了业踏,大家應(yīng)該都有聽(tīng)過(guò)本地方法吧
如何經(jīng)常用的線程類
new Thread().start();
public synchronized void start() {
? ? ? ? if (threadStatus != 0)
? ? ? ? ? ? throw new IllegalThreadStateException();
? ? ? ? group.add(this);
? ? ? ? boolean started = false;
? ? ? ? try {
? ? ? ? ? ? start0();
? ? ? ? ? ? started = true;
? ? ? ? } finally {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? if (!started) {
? ? ? ? ? ? ? ? ? ? group.threadStartFailed(this);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (Throwable ignore) {
? ? ? ? ? ? }
? ? ? ? }
? ? }
其中底層調(diào)用了一個(gè)start0()的方法
private native void start0();
這個(gè)方法沒(méi)有實(shí)現(xiàn)禽炬,但又不是接口,是使用native修飾的勤家,是屬于本地方法腹尖,底層通過(guò)C語(yǔ)言實(shí)現(xiàn)的,那java代碼里為什么會(huì)有C語(yǔ)言實(shí)現(xiàn)的本地方法呢伐脖?
大家都知道JAVA是問(wèn)世的桐臊,在那之前一個(gè)公司的系統(tǒng)百分之九十九都是使用C語(yǔ)言實(shí)現(xiàn)的,但是java出現(xiàn)后晓殊,很多項(xiàng)目都要轉(zhuǎn)為java開(kāi)發(fā)断凶,那么新系統(tǒng)和舊系統(tǒng)就免不了要有交互,那么就需要本地方法來(lái)實(shí)現(xiàn)了巫俺,底層是調(diào)用C語(yǔ)言中的dll庫(kù)文件认烁,就類似于java中的jar包,當(dāng)然介汹,如今跨語(yǔ)言的交互方式就很多了却嗡,比如thrift,http接口方式嘹承,webservice等窗价,當(dāng)時(shí)并沒(méi)有這些方式,就只能通過(guò)本地方法來(lái)實(shí)現(xiàn)了叹卷。
那么本地方法始終也是方法撼港,每個(gè)線程在運(yùn)行的時(shí)候坪它,如果有運(yùn)行到本地方法,那么必然也要產(chǎn)生局部變量等帝牡,那么就需要存儲(chǔ)在本地方法棧了往毡。如果沒(méi)有本地方法,也就沒(méi)有本地方法棧了靶溜。
5.堆
最后我們講堆开瞭,堆是最重要的一塊內(nèi)存區(qū)域,我相信大部分人對(duì)堆都不陌生罩息。但是對(duì)于它的內(nèi)部結(jié)構(gòu)嗤详,運(yùn)作細(xì)節(jié)想要搞清楚也沒(méi)那么簡(jiǎn)單。
對(duì)于這個(gè)基本組成大家應(yīng)該都有所了解瓷炮,對(duì)就是由年輕代和老年代組成葱色,年輕代又分為伊甸園區(qū)和survivor區(qū),survivor區(qū)中又有from區(qū)和to區(qū).
我們new出來(lái)的對(duì)象大家都知道是放在堆中崭别,那具體放在堆中的哪個(gè)位置呢冬筒?
其實(shí)new出來(lái)的對(duì)象一般都放在Eden區(qū),那么為什么叫伊甸園區(qū)呢茅主,伊甸園就是亞當(dāng)夏娃住的地方舞痰,不就是造人的地方么?所以我們new出來(lái)的對(duì)象就是放在這里的诀姚,那當(dāng)Eden區(qū)滿了之后呢响牛?
假設(shè)我們給對(duì)分配600M內(nèi)存,這個(gè)是可以通過(guò)參數(shù)調(diào)節(jié)的赫段,我們后文再講呀打。那么老年代默認(rèn)是占2/3的,也就是差不多400M糯笙,那年輕代就是200M贬丛,Eden區(qū)160M,Survivor區(qū)40M给涕。
GC
一個(gè)程序只要在運(yùn)行豺憔,那么就不會(huì)不停的new對(duì)象,那么總有一刻Eden區(qū)會(huì)放滿够庙,那么一旦Eden區(qū)被放滿之后恭应,虛擬機(jī)會(huì)干什么呢?沒(méi)錯(cuò)耘眨,就是gc昼榛,不過(guò)這里的gc屬于minor gc,就是垃圾收集剔难,來(lái)收集垃圾對(duì)象并清理的胆屿,那么什么是垃圾對(duì)象呢奥喻?
好比我們上面說(shuō)的math對(duì)象,我們假設(shè)我們是一個(gè)web應(yīng)用程序莺掠,main線程執(zhí)行完之后程序不會(huì)結(jié)束衫嵌,但是main方法結(jié)束了读宙,那么main()方法棧幀會(huì)被釋放彻秆,局部變量會(huì)被釋放,但是局部變量對(duì)應(yīng)的堆中的對(duì)象還是依然存在的结闸,但是又沒(méi)有指針指向它唇兑,那么它就是一個(gè)垃圾對(duì)象,那就應(yīng)該被回收掉了桦锄,之后如果還會(huì)new Math對(duì)象扎附,也不會(huì)用這個(gè)之前的了,因?yàn)橐呀?jīng)無(wú)法找到它了结耀,如果留著這個(gè)對(duì)象只會(huì)占用內(nèi)存留夜,顯然是不合適的。
這里就涉及到了一個(gè)GC Root根以及可達(dá)性分析算法的概念图甜,也是面試偶爾會(huì)被問(wèn)到的碍粥。
可達(dá)性分析算法是將GC Roots對(duì)象作為起點(diǎn),從這些起點(diǎn)開(kāi)始向下搜索引用的對(duì)象黑毅,找到的對(duì)象都標(biāo)記為非垃圾對(duì)象嚼摩,其余未標(biāo)記的都是垃圾對(duì)象。
那么GC Roots根對(duì)象又是什么呢矿瘦,可以作為根節(jié)點(diǎn)的有線程棧的本地變量枕面,靜態(tài)變量,本地方法棧的變量等等缚去,說(shuō)白了就是找到和根節(jié)點(diǎn)有聯(lián)系的對(duì)象潮秘,也就是有用的,其余都認(rèn)為是垃圾對(duì)象來(lái)回收易结。
經(jīng)歷了第一次minor gc后枕荞,沒(méi)有被清理的對(duì)象就會(huì)被移到From區(qū),如上圖衬衬。
上面在說(shuō)對(duì)象組成的時(shí)候有寫(xiě)到买猖,在對(duì)象頭的Mark Word中有存儲(chǔ)GC分代年齡,一個(gè)對(duì)象每經(jīng)歷一次gc滋尉,那么它的gc分代年齡就會(huì)+1玉控,如上圖。
那么如果第二次新的對(duì)象又把Eden區(qū)放滿了狮惜,那么又會(huì)執(zhí)行minor gc高诺,但是這次會(huì)連著From區(qū)一起gc碌识,然后將Eden區(qū)和From區(qū)存活的對(duì)象都移到To區(qū)域,對(duì)象頭中分代年齡都+1虱而。
那么當(dāng)?shù)谌蜤den區(qū)又滿的時(shí)候筏餐,minor gc就是回收Eden區(qū)和To區(qū)域了,TEden區(qū)和To區(qū)域還活著的對(duì)象就會(huì)都移到From區(qū)牡拇,如上圖魁瞪。說(shuō)白了就是Survivor區(qū)中總有一塊區(qū)域是空著的,存活的對(duì)象存放是在From區(qū)和To區(qū)輪流存放惠呼,也就是互相復(fù)制拷貝导俘,這也就是垃圾回收算法中的復(fù)制-回收算法。
如果一個(gè)對(duì)象經(jīng)歷了一個(gè)限值15次gc的時(shí)候剔蹋,就會(huì)移至老年代旅薄。那如果還沒(méi)有到限值,F(xiàn)rom區(qū)或者To區(qū)域也放不下了泣崩,就會(huì)直接挪到老年代少梁,這只是舉例了兩種常規(guī)規(guī)則,還有其他規(guī)則也是會(huì)把對(duì)象存放至老年代的矫付。
那么隨著應(yīng)用程序的不斷運(yùn)行凯沪,老年代最終也是會(huì)滿的,那么此時(shí)也會(huì)gc技即,此時(shí)的gc就是Full gc了著洼。