一、JVM對(duì)象創(chuàng)建
在Java程序中,對(duì)象的創(chuàng)建是通過類加載器來實(shí)現(xiàn)的。JVM在創(chuàng)建對(duì)象之前,會(huì)先加載該對(duì)象所屬的類,然后再創(chuàng)建對(duì)象。
1. 類加載器
Java中的類加載器是用來加載類的,它將類的字節(jié)碼文件加載到內(nèi)存中,并生成對(duì)應(yīng)的Class對(duì)象。Java中的類加載器有三種:
1)Bootstrap ClassLoader:它是JVM的內(nèi)置類加載器,用來加載Java的核心類庫,如java.lang和java.util等。
2)Extension ClassLoader:它用來加載Java的擴(kuò)展類庫,如javax等。
3)System ClassLoader:它用來加載應(yīng)用程序的類,如自定義的類等。
2. 類加載過程
類加載的過程可以分為三個(gè)步驟:加載、鏈接和初始化。
1)加載:類加載器首先會(huì)從文件系統(tǒng)、網(wǎng)絡(luò)或其他來源中加載類的字節(jié)碼文件。在加載過程中,類加載器會(huì)生成對(duì)應(yīng)的Class對(duì)象,并將其存放在JVM的方法區(qū)中。
2)鏈接:鏈接分為三個(gè)階段:驗(yàn)證、準(zhǔn)備和解析。
驗(yàn)證:驗(yàn)證階段用來確保類的字節(jié)碼文件符合JVM規(guī)范,并且不包含安全漏洞等問題。
準(zhǔn)備:準(zhǔn)備階段用來為類的靜態(tài)變量分配內(nèi)存,并設(shè)置默認(rèn)值。
解析:解析階段用來將類的符號(hào)引用轉(zhuǎn)換為直接引用。
3)初始化:初始化階段用來執(zhí)行類的靜態(tài)初始化代碼塊,并初始化靜態(tài)變量。在初始化階段,JVM會(huì)按照靜態(tài)變量的定義順序執(zhí)行靜態(tài)初始化代碼塊。
3. JVM對(duì)象創(chuàng)建時(shí)類加載的過程
在JVM對(duì)象創(chuàng)建時(shí),類加載器會(huì)先加載該對(duì)象所屬的類,然后再創(chuàng)建對(duì)象。具體的過程如下:
1)檢查類是否已經(jīng)被加載,如果沒有,就先加載該類。
2)在堆內(nèi)存中分配一塊空間來存放對(duì)象。
3)對(duì)對(duì)象進(jìn)行初始化,包括設(shè)置對(duì)象的成員變量和執(zhí)行構(gòu)造函數(shù)。
4)返回對(duì)象引用。
二、內(nèi)存分配機(jī)制
1. JVM內(nèi)存劃分如下圖

程序計(jì)數(shù)器
程序計(jì)數(shù)器是JVM中的一塊較小的內(nèi)存區(qū)域,它用于記錄當(dāng)前線程所執(zhí)行的字節(jié)碼的位置。由于Java是一種解釋性語言,每條指令都需要翻譯成機(jī)器碼后才能執(zhí)行,因此程序計(jì)數(shù)器就是記錄正在執(zhí)行的字節(jié)碼指令的位置的指針。
Java虛擬機(jī)棧
Java虛擬機(jī)棧是線程私有的,用于存儲(chǔ)方法調(diào)用過程中的局部變量、方法參數(shù)和返回值等數(shù)據(jù)。每個(gè)方法在執(zhí)行的時(shí)候都會(huì)在棧上創(chuàng)建一個(gè)棧幀,棧幀中包含了該方法的局部變量表、操作數(shù)棧、方法返回地址等信息。隨著方法的執(zhí)行結(jié)束,棧幀也會(huì)隨之出棧銷毀。
本地方法棧
本地方法棧與Java虛擬機(jī)棧類似,不同的是本地方法棧是為虛擬機(jī)使用到的本地(Native)方法服務(wù)的。
Java堆
Java堆是Java虛擬機(jī)管理的最大一塊內(nèi)存區(qū)域,被所有線程共享。Java堆是用來存儲(chǔ)對(duì)象實(shí)例和數(shù)組對(duì)象的內(nèi)存區(qū)域,由于垃圾回收器會(huì)在Java堆中進(jìn)行垃圾回收,因此Java堆被劃分為年輕代和老年代兩部分。
年輕代
年輕代是Java堆中的一部分,它又被分為三個(gè)部分:Eden空間、Survivor空間From和Survivor空間To。每個(gè)對(duì)象在創(chuàng)建的時(shí)候都會(huì)被分配到Eden空間,當(dāng)Eden空間滿了之后,虛擬機(jī)會(huì)對(duì)Eden空間中的存活對(duì)象進(jìn)行一次垃圾回收,將存活的對(duì)象復(fù)制到Survivor空間From中,并將Eden空間中的所有對(duì)象清空。當(dāng)Survivor空間From也滿了之后,虛擬機(jī)會(huì)對(duì)Survivor空間From和Survivor空間To中的存活對(duì)象進(jìn)行一次垃圾回收,將存活的對(duì)象復(fù)制到Survivor空間To中,并將Survivor空間From中的所有對(duì)象清空。當(dāng)對(duì)象在Survivor空間中存活了一定的次數(shù)之后,虛擬機(jī)會(huì)將其晉升到老年代中。
老年代
老年代是Java堆中的一部分,用于存儲(chǔ)長時(shí)間存活的對(duì)象。在進(jìn)行垃圾回收時(shí),虛擬機(jī)會(huì)對(duì)老年代中的存活對(duì)象進(jìn)行標(biāo)記-清除、標(biāo)記-整理等垃圾回收算法。
方法區(qū)
方法區(qū)也是被所有線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在JDK8之前,方法區(qū)被稱為永久代(PermGen),但是永久代容易出現(xiàn)內(nèi)存溢出的問題,因此在JDK8之后被移除,被元空間(Metaspace)取代。
2. JVM內(nèi)存分配
在JVM中,對(duì)象的內(nèi)存分配是由Java堆中的內(nèi)存分配器負(fù)責(zé)的。Java堆中的內(nèi)存分配器主要分為兩種:Serial收集器和并行收集器。
Serial收集器
Serial收集器是一種單線程的垃圾回收器,它會(huì)暫停所有Java線程來進(jìn)行垃圾回收。Serial收集器主要適用于單核CPU和小內(nèi)存的環(huán)境,它的優(yōu)點(diǎn)是簡單高效,但是不適合大規(guī)模的應(yīng)用場景。
并行收集器
并行收集器是一種多線程的垃圾回收器,它可以利用多核CPU來并發(fā)地執(zhí)行垃圾回收操作,從而減少垃圾回收的暫停時(shí)間。并行收集器適用于大規(guī)模應(yīng)用和高并發(fā)環(huán)境,但是它也有一些缺點(diǎn),例如垃圾回收期間會(huì)消耗大量的CPU資源,可能會(huì)導(dǎo)致應(yīng)用的響應(yīng)時(shí)間變長。
3. 對(duì)象的內(nèi)存分配過程
在JVM中,當(dāng)需要?jiǎng)?chuàng)建一個(gè)新的對(duì)象時(shí),虛擬機(jī)會(huì)首先在Eden空間中查找是否有足夠的空間來存放該對(duì)象。如果Eden空間中的剩余空間不夠,虛擬機(jī)會(huì)觸發(fā)一次Minor GC來對(duì)Eden空間進(jìn)行垃圾回收,從而為該對(duì)象騰出空間。如果Eden空間中的剩余空間足夠,虛擬機(jī)會(huì)在Eden空間中為該對(duì)象分配內(nèi)存。
當(dāng)對(duì)象在Eden空間中存活了一定的時(shí)間后,虛擬機(jī)會(huì)將其移動(dòng)到Survivor空間中。當(dāng)Survivor空間也滿了之后,虛擬機(jī)會(huì)觸發(fā)一次Minor GC來對(duì)Survivor空間進(jìn)行垃圾回收,從而為對(duì)象騰出空間。如果Survivor空間也不足以存放該對(duì)象,虛擬機(jī)會(huì)將該對(duì)象直接分配到老年代中。
在老年代中分配內(nèi)存時(shí),虛擬機(jī)會(huì)檢查老年代中是否有足夠的連續(xù)空間來存放該對(duì)象。如果老年代中的空間足夠,虛擬機(jī)會(huì)為該對(duì)象分配內(nèi)存。如果老年代中的空間不足,虛擬機(jī)會(huì)觸發(fā)一次Full GC來對(duì)整個(gè)堆空間進(jìn)行垃圾回收,從而為該對(duì)象騰出空間。
三、內(nèi)存分配方式
JVM的內(nèi)存分配方式有兩種:棧內(nèi)存和堆內(nèi)存。棧內(nèi)存是用來存放基本數(shù)據(jù)類型和對(duì)象引用的,而堆內(nèi)存是用來存放對(duì)象的。具體的內(nèi)存分配方式如下:
1)棧內(nèi)存:棧內(nèi)存是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),用來存放基本數(shù)據(jù)類型和對(duì)象引用。當(dāng)程序調(diào)用一個(gè)方法時(shí),JVM會(huì)為該方法創(chuàng)建一個(gè)棧幀,用來存放方法的參數(shù)、局部變量和返回值等信息。當(dāng)方法執(zhí)行完畢時(shí),JVM會(huì)將該棧幀出棧,釋放棧內(nèi)存。
2)堆內(nèi)存:堆內(nèi)存是用來存放對(duì)象的。當(dāng)程序創(chuàng)建對(duì)象時(shí),JVM會(huì)在堆內(nèi)存中分配一塊空間來存放對(duì)象。堆內(nèi)存的大小可以通過JVM參數(shù)進(jìn)行調(diào)整。
對(duì)于堆內(nèi)存還存在兩種內(nèi)存分配方式:指針碰撞和空閑列表
1. 指針碰撞
指針碰撞是一種內(nèi)存分配方式,它適用于堆內(nèi)存中的連續(xù)空間。在這種情況下,JVM會(huì)將堆內(nèi)存劃分為兩個(gè)區(qū)域:一部分用來存放已經(jīng)分配的對(duì)象,另一部分則是未分配的空間。JVM會(huì)使用一個(gè)指針來記錄已經(jīng)分配的空間的末尾位置,當(dāng)程序需要?jiǎng)?chuàng)建一個(gè)對(duì)象時(shí),JVM會(huì)檢查未分配的空間是否有足夠的空間來存放該對(duì)象,如果有,JVM會(huì)將指針移動(dòng)到未分配空間的起始位置,并將該對(duì)象存放在指針?biāo)赶虻奈恢?。如果沒有足夠的空間,JVM會(huì)觸發(fā)一次垃圾回收操作,釋放一些已經(jīng)不再使用的空間,然后再次嘗試分配空間。
2. 空閑列表
空閑列表是一種內(nèi)存分配方式,它適用于堆內(nèi)存中的非連續(xù)空間。在這種情況下,JVM會(huì)將堆內(nèi)存劃分為多個(gè)大小不同的塊,每個(gè)塊都有一個(gè)頭部信息,用來記錄該塊是否已經(jīng)被分配和塊的大小等信息。JVM會(huì)使用一個(gè)鏈表來記錄所有未被分配的塊,當(dāng)程序需要?jiǎng)?chuàng)建一個(gè)對(duì)象時(shí),JVM會(huì)遍歷空閑列表,查找是否有足夠大小的塊來存放該對(duì)象,如果有,JVM會(huì)將該塊分配給該對(duì)象,并將該塊從空閑列表中移除。如果沒有足夠的空間,JVM會(huì)觸發(fā)一次垃圾回收操作,釋放一些已經(jīng)不再使用的空間,然后再次嘗試分配空間。
棧內(nèi)存用來存放基本數(shù)據(jù)類型和對(duì)象引用,堆內(nèi)存用來存放對(duì)象。
指針碰撞適用于堆內(nèi)存中的連續(xù)空間,空閑列表適用于堆內(nèi)存中的非連續(xù)空間。
四、對(duì)象頭
在JVM中,每個(gè)對(duì)象都有一個(gè)對(duì)象頭,它是對(duì)象在內(nèi)存中的元數(shù)據(jù)信息,包括對(duì)象的類型、鎖狀態(tài)、GC信息等。

1. JVM對(duì)象頭的結(jié)構(gòu)
JVM對(duì)象頭的結(jié)構(gòu)包含兩部分:Mark Word和Class Pointer。其中,Mark Word是對(duì)象頭的核心部分,它占用了8個(gè)字節(jié),Class Pointer占用了4個(gè)字節(jié)。下面分別介紹這兩部分的作用和結(jié)構(gòu)。
-
Mark Word
Mark Word是對(duì)象頭的核心部分,它包含了對(duì)象的類型、鎖狀態(tài)、GC信息等。Mark Word的結(jié)構(gòu)如下:
| 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hashcode | age | biased_lock | lock_state | identity_hashcode | unused | CMS_mark | CMS_remark | CMS_bits | reserved | thread | epoch | unused | unused | forward |
其中,各字段的含義如下:
hashcode:對(duì)象的哈希碼,用于快速定位對(duì)象在哈希表中的位置。
age:對(duì)象的年齡,用于標(biāo)記對(duì)象是否經(jīng)過了一次Minor GC。
biased_lock:偏向鎖標(biāo)記,用于標(biāo)記對(duì)象是否處于偏向鎖狀態(tài)。
lock_state:鎖狀態(tài),用于標(biāo)記對(duì)象的鎖狀態(tài),包括無鎖、輕量級(jí)鎖、重量級(jí)鎖等。
identity_hashcode:對(duì)象的標(biāo)識(shí)哈希碼,用于在JVM中識(shí)別對(duì)象。
unused:未使用字段。
CMS_mark:CMS標(biāo)記位,用于標(biāo)記對(duì)象是否被CMS GC標(biāo)記。
CMS_remark:CMS重新標(biāo)記位,用于標(biāo)記對(duì)象是否被CMS GC重新標(biāo)記。
CMS_bits:CMS位圖,用于標(biāo)記對(duì)象在CMS GC中的狀態(tài)。
reserved:保留字段。
thread:線程ID,用于標(biāo)記當(dāng)前持有鎖的線程ID。
epoch:時(shí)間戳,用于標(biāo)記偏向鎖的時(shí)間戳。
forward:對(duì)象是否被移動(dòng)的標(biāo)記位。
Mark Word的結(jié)構(gòu)可以根據(jù)JVM的版本和配置不同而有所不同,但是大致的結(jié)構(gòu)是相似的。
2. Class Pointer
Class Pointer是對(duì)象頭的另一部分,它占用了4個(gè)字節(jié),用于指向?qū)ο蟮念愒獢?shù)據(jù)信息。在JVM中,每個(gè)類都有一個(gè)Class對(duì)象,它包含了類的基本信息,如類名、父類、接口、方法等。當(dāng)對(duì)象被創(chuàng)建時(shí),JVM會(huì)將Class對(duì)象的指針存儲(chǔ)在對(duì)象頭的Class Pointer中,以便在運(yùn)行時(shí)快速訪問對(duì)象的類信息。
JVM對(duì)象頭的作用
JVM對(duì)象頭是對(duì)象在內(nèi)存中的元數(shù)據(jù)信息,它包含了對(duì)象的類型、鎖狀態(tài)、GC信息等。JVM通過對(duì)象頭來管理對(duì)象的生命周期和狀態(tài),包括對(duì)象的創(chuàng)建、訪問、鎖定、垃圾回收等。下面分別介紹JVM對(duì)象頭的幾個(gè)重要作用。
- 類型標(biāo)記
JVM對(duì)象頭中的Mark Word包含了對(duì)象的類型信息,用于標(biāo)記對(duì)象的類型。在JVM中,每個(gè)對(duì)象都有一個(gè)類型,它決定了對(duì)象的行為和屬性。JVM通過對(duì)象頭中的類型標(biāo)記來判斷對(duì)象的類型,以便在運(yùn)行時(shí)調(diào)用對(duì)象的方法和屬性。
- 鎖狀態(tài)標(biāo)記
JVM對(duì)象頭中的Mark Word包含了對(duì)象的鎖狀態(tài)信息,用于標(biāo)記對(duì)象的鎖狀態(tài)。在JVM中,對(duì)象可以被多個(gè)線程同時(shí)訪問,為了保證線程安全,JVM通過對(duì)象頭中的鎖狀態(tài)標(biāo)記來管理對(duì)象的鎖狀態(tài)。在JVM中,鎖狀態(tài)包括無鎖、輕量級(jí)鎖、重量級(jí)鎖等,不同的鎖狀態(tài)對(duì)應(yīng)不同的鎖實(shí)現(xiàn)方式。
- GC標(biāo)記
JVM對(duì)象頭中的Mark Word包含了對(duì)象的GC信息,用于標(biāo)記對(duì)象的GC狀態(tài)。在JVM中,對(duì)象的內(nèi)存分配和回收是由垃圾回收器來管理的,JVM通過對(duì)象頭中的GC標(biāo)記來管理對(duì)象的GC狀態(tài)。在JVM中,GC標(biāo)記包括標(biāo)記位、位圖等,用于標(biāo)記對(duì)象是否需要被垃圾回收器回收。
- Class Pointer
JVM對(duì)象頭中的Class Pointer用于指向?qū)ο蟮念愒獢?shù)據(jù)信息,以便在運(yùn)行時(shí)快速訪問對(duì)象的類信息。在JVM中,每個(gè)類都有一個(gè)Class對(duì)象,它包含了類的基本信息,如類名、父類、接口、方法等。JVM通過對(duì)象頭中的Class Pointer來訪問對(duì)象的類信息,以便在運(yùn)行時(shí)調(diào)用對(duì)象的方法和屬性。
JVM對(duì)象頭是對(duì)象在內(nèi)存中的元數(shù)據(jù)信息,它包含了對(duì)象的類型、鎖狀態(tài)、GC信息等。JVM通過對(duì)象頭來管理對(duì)象的生命周期和狀態(tài),包括對(duì)象的創(chuàng)建、訪問、鎖定、垃圾回收等。
五、執(zhí)行init方法
在Java中,每個(gè)類都有一個(gè)默認(rèn)的構(gòu)造函數(shù),用于初始化對(duì)象的成員變量。但是,有時(shí)候我們需要在對(duì)象創(chuàng)建時(shí)執(zhí)行一些特殊的操作,比如初始化靜態(tài)變量、讀取配置文件等。這時(shí)候就需要使用init方法來完成這些操作。
1. 什么是init方法
init方法是一種特殊的Java方法,它的作用是在對(duì)象創(chuàng)建后進(jìn)行初始化工作。init方法的命名規(guī)則是以“<init>”開頭,后面跟隨著一些參數(shù),用于初始化對(duì)象的狀態(tài)。init方法的定義在Java代碼中不可見,它是由Java編譯器自動(dòng)產(chǎn)生的。在Java中,每個(gè)類都可以定義一個(gè)init方法,它必須滿足以下條件:
init方法必須是非靜態(tài)方法。
init方法的名稱可以是任意的,但是通常為init或者initialize。
init方法的返回值類型必須為void。
init方法不能被重載。
init方法的訪問權(quán)限可以是public、protected或者默認(rèn)訪問權(quán)限,但是不能是private。
在Java中,init方法通常用于初始化靜態(tài)變量、讀取配置文件、建立數(shù)據(jù)庫連接等。在Spring框架中,init方法還被用于執(zhí)行Bean的初始化操作。
2. JVM執(zhí)行init方法的過程
當(dāng)JVM執(zhí)行new關(guān)鍵字創(chuàng)建一個(gè)對(duì)象時(shí),會(huì)分配一塊內(nèi)存空間用于存儲(chǔ)對(duì)象的實(shí)例變量,并初始化對(duì)象頭信息。在初始化完對(duì)象頭信息后,JVM會(huì)自動(dòng)調(diào)用該對(duì)象的init方法進(jìn)行進(jìn)一步的初始化工作。
init方法的調(diào)用過程是由Java虛擬機(jī)完成的,其具體過程如下:
在堆上分配對(duì)象的內(nèi)存空間,包括對(duì)象頭和實(shí)例變量。
對(duì)象頭信息初始化完成后,JVM會(huì)將對(duì)象的引用作為參數(shù)傳遞給init方法。
JVM會(huì)查找對(duì)象所屬類的init方法。如果該類沒有定義init方法,則會(huì)向上查找其父類的init方法,直到Object類為止。
JVM在找到init方法后,會(huì)將對(duì)象引用作為參數(shù)傳遞給init方法,并調(diào)用該方法進(jìn)行對(duì)象的進(jìn)一步初始化工作。
當(dāng)init方法執(zhí)行完畢后,JVM將該對(duì)象的引用返回給程序,使得程序可以通過該引用來訪問對(duì)象。
需要注意的是,JVM只會(huì)調(diào)用一個(gè)對(duì)象的init方法一次。如果一個(gè)對(duì)象被多次創(chuàng)建,它的init方法也只會(huì)被調(diào)用一次。另外,JVM會(huì)確保在調(diào)用init方法之前,對(duì)象的實(shí)例變量已經(jīng)被正確初始化。
JVM在執(zhí)行init方法時(shí)是單線程的,即同一時(shí)間只有一個(gè)線程在執(zhí)行init方法。這是因?yàn)閷?duì)象的初始化操作通常是非常耗時(shí)的,如果允許多個(gè)線程同時(shí)執(zhí)行init方法,就會(huì)導(dǎo)致競爭條件和線程安全問題。
總結(jié):
JVM的對(duì)象創(chuàng)建、內(nèi)存分配機(jī)制、內(nèi)存分配方式、設(shè)置對(duì)象頭、執(zhí)行init方法是Java程序中非常重要的一部分。了解這些機(jī)制可以幫助我們更好地理解Java程序的運(yùn)行機(jī)制,從而更好地編寫高質(zhì)量的Java程序。在實(shí)際開發(fā)中,我們應(yīng)該注意對(duì)象的創(chuàng)建和內(nèi)存分配,避免對(duì)象的過多創(chuàng)建和內(nèi)存泄漏等問題,從而提高程序的性能和穩(wěn)定性。