你真的了解一段Java程序的生命史嗎
作為一名程序猿 ,我們每天都在寫Code,但你真的了解它的生命周期么?今天就來(lái)簡(jiǎn)單聊下它的生命歷程,說(shuō)起一段Java Code,從出生到game over大體分這么幾步:編譯、類加載、運(yùn)行、GC。
Java語(yǔ)言的編譯期其實(shí)是一段“不確定 ”的過(guò)程,因?yàn)榭赡苁且粋€(gè)前端編譯器把.java文件轉(zhuǎn)變?yōu)?class文件的過(guò)程;也可能是指JVM的后端運(yùn)行期編譯器(JIT編譯器)把字節(jié)碼轉(zhuǎn)變?yōu)闄C(jī)器碼的過(guò)程;還可能是指使用靜態(tài)提前編譯器(AOT編譯器)直接把.java文件編譯成本地機(jī)器碼的過(guò)程。但是在這里我們說(shuō)的是第一類。也是符合我們大眾對(duì)編譯認(rèn)知的。編譯在這個(gè)時(shí)間段經(jīng)歷了哪些過(guò)程呢?
詞法、語(yǔ)法分析詞法分析是將源代碼的字符流轉(zhuǎn)變?yōu)門oken集合,而語(yǔ)法分析則是根據(jù)Token序列抽象構(gòu)造語(yǔ)法樹(ATS)的過(guò)程,ATS是一種用來(lái)描述程序代碼語(yǔ)法結(jié)構(gòu)的樹形表示形式,語(yǔ)法樹的每個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu),例如包、類型、修飾符、運(yùn)算符、接口、返回值甚至代碼注釋都可以是一個(gè)語(yǔ)法結(jié)構(gòu)。
填充符號(hào)表完成了語(yǔ)法和詞法分析之后,下一步就是填充符號(hào)表的過(guò)程,符號(hào)表中所登記的信息在編譯的不同階段都要用到。在這里延伸一下符號(hào)表的概念。符號(hào)表是什么呢?它是由一組符號(hào)地址和符號(hào)信息構(gòu)成的表格,最簡(jiǎn)單的可以理解為哈希表的K-V值對(duì)的形式。為什么會(huì)用到符號(hào)表呢?符號(hào)表最早期的應(yīng)用之一就是組織程序代碼的信息。最初,計(jì)算機(jī)程序只是一串簡(jiǎn)單的數(shù)字,但程序猿們很快發(fā)現(xiàn)使用符號(hào)來(lái)表示操作和內(nèi)存地址(變量名)要方便得多。將名稱和數(shù)字關(guān)聯(lián)起來(lái)就需要一張符號(hào)表。隨著程序的增長(zhǎng),符號(hào)表操作的性能逐漸變成了程序開發(fā)效率的瓶頸,為此從而誕生了許多提升序號(hào)表效率的數(shù)據(jù)結(jié)構(gòu)和算法。至于所謂的數(shù)據(jù)結(jié)構(gòu)和算法有哪些呢?大體說(shuō)下:無(wú)序鏈表中的順序查找、有序數(shù)組中的二分查找、二叉查找樹、平衡查找樹(在這我們主要接觸到的是紅黑樹)、散列表(基于拉鏈法的散列表,基于線性探測(cè)法的散列表)。像Java中的java.util.TreeMap和java.util.HashMap分別是基于紅黑樹和拉鏈法的散列表的符號(hào)表實(shí)現(xiàn)的。這里提到的符號(hào)表的概念不再細(xì)說(shuō),感興趣的可以查找相關(guān)資料。
語(yǔ)義分析經(jīng)過(guò)上兩步之后,我們獲得了程序代碼的抽象語(yǔ)法樹表示,語(yǔ)法樹能表示一個(gè)正確的源代碼抽象,但無(wú)法保證源程序是符合邏輯的,這時(shí)候語(yǔ)義分析登場(chǎng)了,它的主要任務(wù)就是對(duì)結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查。標(biāo)注檢查、數(shù)據(jù)及控制流分析、解語(yǔ)法糖是語(yǔ)義分析階段的幾個(gè)步驟,在這具體說(shuō)下語(yǔ)法糖的概念。語(yǔ)法糖是指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響,但更方便程序猿使用。Java中最常用的語(yǔ)法糖主要是泛型、變長(zhǎng)參數(shù)、自從裝箱/拆箱、遍歷循環(huán),JVM在運(yùn)行時(shí)不支持這些語(yǔ)法,它們?cè)诰幾g階段還原回簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程也就是解語(yǔ)法糖。舉個(gè)泛型擦除的例子,List<Integer>和List<String>在編譯之后會(huì)進(jìn)行泛型擦除,變成一樣的原生類型List<E>。
字節(jié)碼生成字節(jié)碼生成是Javac編譯過(guò)程的最后一個(gè)階段,在這個(gè)階段會(huì)把前面各步驟生成的信息轉(zhuǎn)化成字節(jié)碼寫到磁盤中,還會(huì)進(jìn)行了少量代碼添加和轉(zhuǎn)換的工作。實(shí)例構(gòu)造器<init>()方法和類構(gòu)造器<clinit>()方法(這里的實(shí)例構(gòu)造器并不是指默認(rèn)構(gòu)造函數(shù),如果用戶代碼沒(méi)有提供任何構(gòu)造函數(shù),那編譯器將會(huì)添加一個(gè)沒(méi)有參數(shù)的、訪問(wèn)性與當(dāng)前類一致的默認(rèn)構(gòu)造函數(shù),這個(gè)工作在填充符號(hào)表階段已經(jīng)完成,而類構(gòu)造器<clinit>()方法指的是編譯器自動(dòng)收集類中的所有類變量賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的)就是在這個(gè)階段添加到語(yǔ)法樹中的。到此為止整個(gè)編譯過(guò)程結(jié)束。
類加載編譯將程序編譯成字節(jié)碼之后,下一步就是類加載到內(nèi)存的過(guò)程。
類加載的過(guò)程是在虛擬機(jī)內(nèi)存的方法區(qū)進(jìn)行,這地方涉及到虛擬機(jī)內(nèi)存,所以在這首先簡(jiǎn)單介紹下程序在內(nèi)存區(qū)域分布的概念。虛擬機(jī)內(nèi)存區(qū)域劃分為:程序計(jì)數(shù)器、棧、本地方法棧、堆、方法區(qū)(部分區(qū)域?yàn)檫\(yùn)行時(shí)常量池)、直接內(nèi)存。
程序計(jì)數(shù)器程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在JVM概念模型中,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。
棧棧用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。其中局部變量表存放了編譯期克制的各種基本數(shù)據(jù)類型、對(duì)象引用。它與程序計(jì)數(shù)器一樣都是線程私有的。
本地方法棧本地方法棧與上面介紹的虛擬機(jī)棧作用相似,它們的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用的Native方法服務(wù),甚至有的虛擬機(jī)會(huì)把這兩塊合二為一。
堆堆是JVM管理內(nèi)存最大的一塊。它是被所有線程共享的一塊區(qū)域,它的唯一目的是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存(像特殊的類對(duì)象會(huì)在方法區(qū)分配內(nèi)存)。這地方也是垃圾收集管理的主要區(qū)域,從內(nèi)存回收角度看,現(xiàn)在垃圾收集器都采用分代收集算法(后面會(huì)詳細(xì)介紹),所以Java堆還可以進(jìn)一步細(xì)分:新生代和老年代,而新生代進(jìn)一步細(xì)分:Eden空間、From Survivor空間、To Survivor空間。為了效率考慮,堆還可能劃分為多個(gè)線程私有的分配緩沖區(qū)(TLAB)。無(wú)論如何劃分,都與存放內(nèi)容無(wú)關(guān),無(wú)論哪個(gè)區(qū)域,存放的依然是對(duì)象實(shí)例,它們存在的目的只是為了更好的回收和分配內(nèi)存而已。
方法區(qū)方法區(qū)與堆一樣,都是線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。而運(yùn)行時(shí)常量池是方法區(qū)的一部分,它主要用于存放編譯期聲明各種字面量和符號(hào)引用。
直接內(nèi)存直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也是不Java規(guī)范中定義的內(nèi)存區(qū)域,你可以簡(jiǎn)單理解為堆外內(nèi)存,內(nèi)存分配不受Java堆大小的限制但受整個(gè)內(nèi)存大小的限制。
說(shuō)完了虛擬機(jī)內(nèi)存區(qū)域的概念,我們回到正題,類加載的流程到底是什么呢?加載、驗(yàn)證、準(zhǔn)備、解析、初始化五步。其中加載、驗(yàn)證、準(zhǔn)備、初始化是順序執(zhí)行的,而解析則不一定,它有可能會(huì)在初始化之后執(zhí)行。
加載在加載階段,JVM需要完成三個(gè)步驟:首先通過(guò)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流,然后將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),最后在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)入口。在第一步獲取二進(jìn)制字節(jié)流中并沒(méi)有明確指出從一個(gè)*.class文件中獲取,規(guī)定的靈活性導(dǎo)致我們可以從ZIP(為JAR、EAR/WAR格式提供基礎(chǔ))包中獲取,從網(wǎng)絡(luò)獲取(Applet),運(yùn)行時(shí)計(jì)算生成(動(dòng)態(tài)代理),其他文件產(chǎn)生(JSP文件生成的Class類),從數(shù)據(jù)庫(kù)獲取。
驗(yàn)證驗(yàn)證,顧名思義,其實(shí)就是為了確保Class文件字節(jié)流中包含信息符合JVM的要求,因?yàn)镃lass文件的來(lái)源途徑不一定中規(guī)中矩的從編譯器產(chǎn)生,也有可能用十六進(jìn)制編輯器直接編寫Class文件。校驗(yàn)流程為文件格式校驗(yàn)、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證,這地方的具體安全校驗(yàn)方式不再細(xì)說(shuō)。
準(zhǔn)備準(zhǔn)備階段正式為類變量分配內(nèi)存并設(shè)置初始值的階段,這些變量所使用的內(nèi)存都在方法區(qū)進(jìn)行分配。
解析解析階段是JVM將常量池內(nèi)的符號(hào)引用替換為直接引用(指向目標(biāo)的指針、相對(duì)偏移量或句柄)的過(guò)程,前面我們談到的編譯填充符號(hào)表的價(jià)值在這地方體現(xiàn)出來(lái)了。解析過(guò)程無(wú)非就是對(duì)類或接口、字段、接口方法進(jìn)行解析。
初始化類初始化階段是類加載過(guò)程的最后一步,在準(zhǔn)備階段,變量已經(jīng)賦過(guò)一次初始值,而在這一步,則會(huì)根據(jù)程序猿定制的要求進(jìn)行初始化類變量和其他資源。在這個(gè)階段就是執(zhí)行前面編譯字節(jié)碼生成流程提到的<clinit>()方法的過(guò)程。虛擬機(jī)也保證在多線程環(huán)境下這個(gè)方法被同時(shí)調(diào)用時(shí)被正確的加鎖、同步,保證只有一個(gè)線程去執(zhí)行這個(gè)方法而其他線程阻塞等待,筆者以前寫的一篇文章《從一個(gè)簡(jiǎn)單的Java單例示例談?wù)劜l(fā)》中,基于類初始化的單例線程安全的寫法涉及到的就是這塊,有興趣的可以結(jié)合起來(lái)一起看看。這地方還涉及到另一個(gè)我們比較關(guān)心的知識(shí)點(diǎn),Java何時(shí)觸發(fā)對(duì)類的初始化操作呢?
遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有初始化,則需要觸發(fā)其初始化,前面各種叉叉指令什么鬼,簡(jiǎn)單理解就是new一個(gè)對(duì)象的時(shí)候,讀取或者設(shè)置一個(gè)類的靜態(tài)字段的時(shí)候,調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有初始化,則需要觸發(fā)其初始化。當(dāng)初始化一個(gè)類,發(fā)現(xiàn)其父類還沒(méi)進(jìn)行初始化,則先觸發(fā)其父類的初始化操作。當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(main方法所在類),虛擬機(jī)會(huì)先初始化這個(gè)主類。當(dāng)使用JDK1.7以上的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)類沒(méi)有進(jìn)行初始化,則觸發(fā)初始化操作。 運(yùn)行經(jīng)過(guò)了上面兩個(gè)階段,程序開始正常跑起來(lái)了,我們都知道程序執(zhí)行過(guò)程涉及到了各種指令的計(jì)算操作, 程序如何執(zhí)行的呢?這地方就會(huì)使用到文章開頭談到的后端編譯器(JIT即時(shí)編譯器)+解釋器這種搭配使用的混合模式(HotSpot虛擬機(jī)默認(rèn)采用了解釋器與一個(gè)編譯器),字節(jié)碼執(zhí)行引擎則負(fù)責(zé)著這類各種程序計(jì)算操作的任務(wù),它在執(zhí)行Java代碼的時(shí)候有可能會(huì)有解釋執(zhí)行(通過(guò)解釋器執(zhí)行)和編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇,也可能兩者兼?zhèn)洹怯糜谥С痔摂M機(jī)進(jìn)行方法調(diào)用和執(zhí)行的數(shù)據(jù)結(jié)構(gòu),具體的壓棧彈棧各種指令計(jì)算的思路涉及到了一個(gè)經(jīng)典的算法——Dijkstra算法,至于如何執(zhí)行有興趣的自己查資料吧這地方不會(huì)過(guò)多深入。運(yùn)行期的優(yōu)化問(wèn)題在這個(gè)階段同樣重要,而JVM設(shè)計(jì)團(tuán)隊(duì)則把對(duì)性能的優(yōu)化集中到了這個(gè)階段,這樣可以讓那些不是由Javac產(chǎn)生的Class文件同樣享受到編譯器優(yōu)化帶來(lái)的好處,至于具體的優(yōu)化技術(shù)有哪些呢?有很多,這里簡(jiǎn)單提幾個(gè)具有代表性的優(yōu)化技術(shù):公共子表達(dá)式消除、數(shù)組邊界檢查消除、方法內(nèi)聯(lián)、逃逸分析等等。
GC終于說(shuō)到程序要進(jìn)入死亡階段了。JVM是如何判斷程序藥丸的呢?這地方其實(shí)采用了可達(dá)性分析算法,這個(gè)算法的基本思路是通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這個(gè)節(jié)點(diǎn)開始向下搜索,搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)(用圖論話說(shuō),就是從GC Roots到這個(gè)對(duì)象不可達(dá)),則證明此對(duì)象不可用,這時(shí)候就被判定為可回收的對(duì)象。當(dāng)我們已經(jīng)知道要回收的對(duì)象何時(shí)觸發(fā)垃圾收集呢?安全點(diǎn),安全點(diǎn)就是一些讓程序暫定執(zhí)行從而進(jìn)行GC的位置,由此我們很容易知道GC停頓的時(shí)間是垃圾收集的核心。所有的垃圾收集算法以及衍生出來(lái)的垃圾收集器無(wú)不圍繞著盡量減少GC停頓時(shí)間產(chǎn)生的,現(xiàn)在最新的G1垃圾收集器可以建立可預(yù)測(cè)的停頓時(shí)間模型,有計(jì)劃的避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。前文介紹內(nèi)存區(qū)域分布的概念的時(shí)候,我們談到了新生代、老年代,而不同的垃圾收集器有可能作用于新生代,也有可能作用于老年代,甚至沒(méi)有分代的概念(比如G1收集器),說(shuō)到這,下面就具體介紹下垃圾收集算法及對(duì)應(yīng)的垃圾收集器
標(biāo)記-清除算法最基礎(chǔ)的收集算法,算法分為標(biāo)記和清除兩個(gè)階段:首先標(biāo)記處所有要回收的對(duì)象,在標(biāo)記完成之后統(tǒng)一回收所有被標(biāo)記的對(duì)象。它最大的不足是效率不高,還會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,這樣導(dǎo)致的問(wèn)題當(dāng)程序運(yùn)行過(guò)程分配較大對(duì)象時(shí),即使堆中還有足夠的內(nèi)存,但是無(wú)法找到足夠的連續(xù)內(nèi)存只能不得不觸發(fā)一次GC操作。這地方對(duì)應(yīng)的垃圾收集器是CMS收集器。
復(fù)制算法復(fù)制算法是為了解決效率問(wèn)題而生的,它可以將可用內(nèi)存容量劃分為大小相等的兩塊,,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。這樣每次會(huì)對(duì)整個(gè)半?yún)^(qū)進(jìn)行GC,并且不會(huì)產(chǎn)生內(nèi)存碎片等問(wèn)題。現(xiàn)在的商業(yè)虛擬機(jī)大多采用這種算法來(lái)回收新生代,另外劃分內(nèi)存比例也不是1:1,像HotSpot默認(rèn)Eden(一塊Eden區(qū))和Survivor(兩塊Survivor區(qū))的大小比例為8:1,每次使用Eden和其中一塊Surviovr區(qū),也就是新生代中可用內(nèi)存空間是整個(gè)新生代的90%,當(dāng)回收時(shí),將Eden和其中一塊Survivor中還存活的對(duì)象一次性復(fù)制到另一塊Survivor中,最后清理掉Eden和剛才用到的Survivor空間,細(xì)心的讀者在這地方也許會(huì)有發(fā)現(xiàn),如果復(fù)制過(guò)程那塊沒(méi)使用的Survivor不夠用怎么辦呢?這時(shí)候需要依賴?yán)夏甏M(jìn)行分配擔(dān)保,擔(dān)保成功就會(huì)將Eden和其中一塊Survivor中還存活的對(duì)象移動(dòng)到老年代中,擔(dān)保失敗就不得不在老年代觸發(fā)一次垃圾回收。這地方延伸一下,新生代垃圾回收稱為Minor GC,因?yàn)镴ava對(duì)象大多朝生夕死的特性,所以Minor GC很頻繁,一般回收速度也快,而老年代垃圾回收稱為Major GC/Full GC,Major GC的速度一般會(huì)比Minor GC的速度慢很多,從前面的分析過(guò)程我們可以輕易的推斷,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨著一次Minor GC,但非絕對(duì),因此我們GC的目的其實(shí)也是通過(guò)調(diào)優(yōu)盡量控制減少M(fèi)ajor GC的頻率。這地方對(duì)應(yīng)的垃圾收集器是Serial收集器、ParNew收集器(Serial收集器多線程版本,可與后面談到的老年代收集器CMS進(jìn)行配合工作)、Parallel Scavenge收集器。
標(biāo)記-整理算法這個(gè)算法是應(yīng)用在老年代垃圾回收的算法,因?yàn)槔夏甏幌駨?fù)制算法那樣回收頻率高,另外它還會(huì)浪費(fèi)空間。標(biāo)記-整理過(guò)程與標(biāo)記-清除差不多,無(wú)非后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清除,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。這地方對(duì)應(yīng)的垃圾收集器是Serial Old收集器、Parallel Old收集器。
分代收集算法當(dāng)前商業(yè)虛擬機(jī)都采用這種算法,它的思想就是我們前面提到的對(duì)堆內(nèi)存區(qū)域進(jìn)行分代,新生代和老年代,不同的區(qū)域采用不同垃圾收集算法。新生代用復(fù)制算法,老年代用標(biāo)記-整理或標(biāo)記-清除算法。
回顧前面扯了這么多,也許大家對(duì)一段Java Code的生命史有點(diǎn)概念了,或者說(shuō)沒(méi)怎么看懂呀,在這地方我們舉個(gè)例子回顧下整個(gè)流程,當(dāng)我們new一個(gè)對(duì)象的時(shí)候,會(huì)經(jīng)歷什么呢?結(jié)合前面所說(shuō)的,JVM遇到一個(gè)new指令時(shí),首先去檢查整個(gè)指令參數(shù)能否在方法區(qū)的常量池定位到一個(gè)類的符號(hào)引用,并且檢查整個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過(guò),如果沒(méi)有,則必須先執(zhí)行對(duì)應(yīng)類加載過(guò)程。類加載檢查通過(guò)后,接下來(lái)JVM將會(huì)為新生對(duì)象分配內(nèi)存,這個(gè)過(guò)程是在堆中進(jìn)行的,分配大小在類加載完成后就可以確定,如果堆內(nèi)存是規(guī)整的,則采用指針移動(dòng)對(duì)象大小相等距離即可,這種分配方式叫“指針碰撞”,如果是零散的,則JVM維護(hù)一個(gè)列表記錄哪些內(nèi)存可用,分配并更新列表記錄,這種方式叫“空閑列表”,至于采用哪種方式,取決于我們前面提到的堆采用了哪種垃圾收集器決定的。劃分完對(duì)象內(nèi)存之后,虛擬機(jī)會(huì)進(jìn)行必要的初始化操作,接下來(lái)需要對(duì)對(duì)象進(jìn)行必要的設(shè)置,這些信息設(shè)置在對(duì)象頭(類元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等等)里面,這些工作完成之后,一個(gè)新對(duì)象產(chǎn)生了,這地方其實(shí)還沒(méi)結(jié)束,再下一步就是調(diào)用<init>()方法進(jìn)行程序猿計(jì)劃的對(duì)對(duì)象字段進(jìn)行的賦值操作,最后設(shè)置棧中的引用指向這個(gè)堆中對(duì)象所在的內(nèi)存地址(直接引用),這時(shí)候一個(gè)真正可用的對(duì)象已經(jīng)產(chǎn)生了,至于后續(xù)對(duì)對(duì)象進(jìn)行的各種操作及最后的死亡就是前面提到的字節(jié)碼執(zhí)行引擎啊GC啊相信大家已不再陌生。
參考本文內(nèi)容參考《深入Java虛擬機(jī)(第2版)》、《算法(第4版)》,感興趣的可自行查閱。
相關(guān)文章:
1. Django視圖類型總結(jié)2. Xml簡(jiǎn)介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理3. Intellij IDEA 關(guān)閉和開啟自動(dòng)更新的提示?4. Ajax引擎 ajax請(qǐng)求步驟詳細(xì)代碼5. 解析原生JS getComputedStyle6. idea重置默認(rèn)配置的方法步驟7. IntelliJ IDEA Java項(xiàng)目手動(dòng)添加依賴 jar 包的方法(圖解)8. Django使用HTTP協(xié)議向服務(wù)器傳參方式小結(jié)9. intellij idea設(shè)置統(tǒng)一JavaDoc模板的方法詳解10. Spring @Profile注解實(shí)現(xiàn)多環(huán)境配置
