Java中對(duì)象的創(chuàng)建和銷毀過(guò)程詳析
前言
在 Java 編程里,對(duì)象的創(chuàng)建和銷毀是基礎(chǔ)且關(guān)鍵的操作,深刻理解這一過(guò)程有助于編寫(xiě)出高效、穩(wěn)定的代碼。下面將詳細(xì)闡述 Java 中對(duì)象的創(chuàng)建和銷毀過(guò)程。
對(duì)象的創(chuàng)建過(guò)程
1. 類加載檢查
當(dāng)代碼中使用 new 關(guān)鍵字創(chuàng)建對(duì)象時(shí),Java 虛擬機(jī)(JVM)首先會(huì)檢查該對(duì)象對(duì)應(yīng)的類是否已經(jīng)被加載到內(nèi)存中。如果尚未加載,JVM 會(huì)通過(guò)類加載器將該類的字節(jié)碼文件加載到內(nèi)存,并對(duì)其進(jìn)行驗(yàn)證、準(zhǔn)備和解析等操作,最終完成類的初始化。例如,當(dāng)執(zhí)行 Person person = new Person(); 時(shí),JVM 會(huì)先確認(rèn) Person 類是否已加載。
2. 分配內(nèi)存
類加載完成后,JVM 會(huì)為新對(duì)象分配內(nèi)存空間。分配內(nèi)存的方式主要有兩種:
- 指針碰撞:假設(shè) Java 堆中的內(nèi)存是規(guī)整的,所有用過(guò)的內(nèi)存放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。
- 空閑列表:如果 Java 堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒(méi)有辦法簡(jiǎn)單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。
選擇哪種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有空間壓縮整理(Compact)的能力決定。
3. 初始化零值
內(nèi)存分配完成后,JVM 會(huì)將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)。這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。例如,int 類型的字段初始值為 0,boolean 類型的字段初始值為 false。
4. 設(shè)置對(duì)象頭
JVM 會(huì)對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭(Object Header)之中。
5. 執(zhí)行 init 方法
在上述工作都完成之后,從 Java 程序的視角看來(lái),對(duì)象已經(jīng)產(chǎn)生了,但從 JVM 的視角來(lái)看,對(duì)象創(chuàng)建才剛剛開(kāi)始——<init> 方法還沒(méi)有執(zhí)行,所有的字段都還為零。所以一般來(lái)說(shuō)(由字節(jié)碼中是否跟隨有 invokespecial 指令所決定),執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init> 方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全被構(gòu)造出來(lái)。
對(duì)象的銷毀過(guò)程
1. 可達(dá)性分析
在 Java 中,對(duì)象的銷毀主要由垃圾回收機(jī)制(GC)負(fù)責(zé)。JVM 會(huì)通過(guò)可達(dá)性分析算法來(lái)判斷對(duì)象是否存活。該算法以一系列被稱為“GC Roots”的對(duì)象為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可達(dá)的,就有可能被回收。常見(jiàn)的 GC Roots 包括:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象。
- 方法區(qū)中常量引用的對(duì)象。
- 本地方法棧中 JNI(即一般說(shuō)的 Native 方法)引用的對(duì)象。
2. 第一次標(biāo)記
當(dāng)對(duì)象被判定為不可達(dá)時(shí),它會(huì)被第一次標(biāo)記。但此時(shí)對(duì)象還不會(huì)被立即回收,而是會(huì)被放入一個(gè)名為 F - Queue 的隊(duì)列中,并由一個(gè)低優(yōu)先級(jí)的線程去執(zhí)行隊(duì)列中對(duì)象的 finalize() 方法(如果對(duì)象重寫(xiě)了該方法)。
3. finalize() 方法執(zhí)行
finalize() 方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì)。在該方法中,對(duì)象可以重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián),例如把自己(this 關(guān)鍵字)賦值給某個(gè)類變量或者對(duì)象的成員變量。如果對(duì)象在 finalize() 方法中成功拯救了自己,那在第二次標(biāo)記時(shí)它將被移除出“即將回收”的集合;如果對(duì)象沒(méi)有逃脫,那基本上它就真的要被回收了。不過(guò)需要注意的是,finalize() 方法的執(zhí)行是不可靠的,JVM 并不保證該方法一定會(huì)被執(zhí)行。
4. 第二次標(biāo)記
如果對(duì)象在 finalize() 方法執(zhí)行后仍然沒(méi)有與 GC Roots 建立引用關(guān)系,它會(huì)被進(jìn)行第二次標(biāo)記。經(jīng)過(guò)第二次標(biāo)記的對(duì)象,就會(huì)被真正地列入可回收對(duì)象的集合。
5. 垃圾回收
當(dāng)垃圾回收器執(zhí)行垃圾回收操作時(shí),會(huì)回收那些經(jīng)過(guò)第二次標(biāo)記的對(duì)象所占用的內(nèi)存空間,將其釋放回 Java 堆中,供后續(xù)新對(duì)象的分配使用。不同的垃圾回收器采用不同的算法和策略來(lái)執(zhí)行垃圾回收,例如標(biāo)記 - 清除算法、標(biāo)記 - 整理算法、復(fù)制算法等。
知識(shí)補(bǔ)充:
在 Java 虛擬機(jī)的堆區(qū),每個(gè)對(duì)象都可能處于以下三種狀態(tài)之一。
1)可觸及狀態(tài):當(dāng)一個(gè)對(duì)象被創(chuàng)建后,只要程序中還有引用變量引用它,那么它就始終處于可觸及狀態(tài)。
2)可復(fù)活狀態(tài):當(dāng)程序不再有任何引用變量引用該對(duì)象時(shí),該對(duì)象就進(jìn)入可復(fù)活狀態(tài)。在這個(gè)狀態(tài)下,垃圾回收器會(huì)準(zhǔn)備釋放它所占用的內(nèi)存,在釋放之前,會(huì)調(diào)用它及其他處于可復(fù)活狀態(tài)的對(duì)象的 finalize() 方法,這些 finalize() 方法有可能使該對(duì)象重新轉(zhuǎn)到可觸及狀態(tài)。
3)不可觸及狀態(tài):當(dāng) Java 虛擬機(jī)執(zhí)行完所有可復(fù)活對(duì)象的 finalize() 方法后,如果這些方法都沒(méi)有使該對(duì)象轉(zhuǎn)到可觸及狀態(tài),垃圾回收器才會(huì)真正回收它占用的內(nèi)存。
總結(jié)
到此這篇關(guān)于Java中對(duì)象的創(chuàng)建和銷毀過(guò)程的文章就介紹到這了,更多相關(guān)Java對(duì)象的創(chuàng)建和銷毀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中將String類型依照某個(gè)字符分割成數(shù)組的方法
下面小編就為大家分享一篇Java中將String類型依照某個(gè)字符分割成數(shù)組的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Spring security BCryptPasswordEncoder密碼驗(yàn)證原理詳解
這篇文章主要介紹了Spring security BCryptPasswordEncoder密碼驗(yàn)證原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Vert.x Circuit Breaker使用及說(shuō)明
Vert.xCircuitBreaker是Vert.x框架中實(shí)現(xiàn)熔斷器模式的組件,通過(guò)配置最大失敗次數(shù)、超時(shí)時(shí)間等參數(shù),保護(hù)非阻塞和異步行為,提升系統(tǒng)韌性,文章詳細(xì)介紹了依賴配置、核心配置、使用方式、狀態(tài)管理、指標(biāo)監(jiān)控與擴(kuò)展使用等方面2026-01-01
基于JSON和java對(duì)象的互轉(zhuǎn)方法
下面小編就為大家?guī)?lái)一篇基于JSON和java對(duì)象的互轉(zhuǎn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
使用java項(xiàng)目搭建一個(gè)netty服務(wù)
這篇文章主要為大家詳細(xì)介紹了如何使用java項(xiàng)目搭建一個(gè)netty服務(wù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-10-10
關(guān)于struts2中Action名字的大小寫(xiě)問(wèn)題淺談
這篇文章主要給大家介紹了關(guān)于struts2中Action名字大小寫(xiě)問(wèn)題的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2017-06-06

