解析java創(chuàng)建對(duì)象的全過(guò)程
一、java創(chuàng)建對(duì)象的幾種方式
1.1、使用new關(guān)鍵字
調(diào)用類的構(gòu)造方法創(chuàng)建對(duì)象

1.2、反射創(chuàng)建對(duì)象
1.2.1、Class.newInstance創(chuàng)建對(duì)象

1.2.2、調(diào)用構(gòu)造器再去創(chuàng)建對(duì)象Constructor.newInstance
先通過(guò)反射獲取類中無(wú)參構(gòu)造器,然后通過(guò)newInstance()獲取對(duì)象

1.3、clone實(shí)現(xiàn)
通過(guò)Clone創(chuàng)建對(duì)象,首先實(shí)體類中必須先實(shí)現(xiàn)Cloneable接口并復(fù)寫Object的clone方法(因?yàn)镺bject的這個(gè)方法是protected的)


1.4、反序列化
序列化:指把 Java 對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程;
反序列化:指把字節(jié)序列恢復(fù)為 Java 對(duì)象的過(guò)程;
此方式需要類先實(shí)現(xiàn)Serializable接口

public class TestStack {
public static void main(String[] args) throws Exception {
File file =new File("M:/Serializable.txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
UserParam userParam =new UserParam("hello");
outputStream.writeObject(userParam);
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserParam userParam1 = (UserParam)objectInputStream.readObject();
userParam1.setNickName("world");
System.out.println(userParam1);
}
}二、創(chuàng)建對(duì)象的過(guò)程
當(dāng)Java虛擬機(jī)遇到一條字節(jié)碼new指令時(shí):
1、檢查類是否已經(jīng)被加載
去常量池中查找該引用所指向的類有沒有被虛擬機(jī)加載,如果沒有被加載,那么會(huì)進(jìn)行類的加載過(guò)程。類的加載過(guò)程需要經(jīng)歷:加載、鏈接、初始化三個(gè)階段。對(duì)象的大小,在類加載完成時(shí)確定。(jdk1.8中,運(yùn)行時(shí)常量池、類常量池存在于方法區(qū)中。)
2、 為對(duì)象分配內(nèi)存空間
JVM為對(duì)象分配空間,即把一塊確定大小的內(nèi)存塊從Java堆中劃分出來(lái)。
2.1、分配空間的方式
1、指針碰撞
假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有被使用過(guò)的內(nèi)存都被放在一邊,空閑的內(nèi)存被放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間方向挪動(dòng)一段與對(duì)象大小相等的距離。
①正常情況

②給對(duì)象分配內(nèi)存后

這種方式的優(yōu)點(diǎn)是工作簡(jiǎn)單,效率高,只需要移動(dòng)指針就可以分配內(nèi)存空間。
缺點(diǎn)也很明顯:由于用指針碰撞分配內(nèi)存空間分為兩步:
- 1、讀取指針當(dāng)前的位置。
- 2、根據(jù)自身大小移動(dòng)指針,不是原子操作,對(duì)象創(chuàng)建在虛擬機(jī)中是非常頻繁的操作,在并發(fā)情況下,會(huì)導(dǎo)致執(zhí)行讀操作或執(zhí)行寫操作的結(jié)果與預(yù)設(shè)的結(jié)果不一致(指針劃分不一致)。
例如:線程A要給對(duì)象分配8kb,讀取到指針當(dāng)前的位置,時(shí)間片用完,切換到線程B,線程B要給它的對(duì)象分配16kb,也讀取到指針當(dāng)前的位置(和線程A讀取到的一樣),將指針向空閑內(nèi)存方向移動(dòng)16kb大小,線程B時(shí)間片用完,切換到線程A繼續(xù)執(zhí)行,由于線程A使用的指針位置還是之前讀到的。(線程不安全問(wèn)題)
③針對(duì)指針碰撞線程不安全,有兩種方案:
1、同步處理(加鎖)分配內(nèi)存空間行為
采用 CAS 分配重試的方式來(lái)保證更新操作的原子性
2、把內(nèi)存分配行為按照線程,劃分在不同的內(nèi)存空間進(jìn)行
- 即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB),哪個(gè)線程要分配內(nèi)存,就在哪個(gè)線程的本地緩沖區(qū)中分配,只有本地緩沖區(qū)用完了,分配新的緩存區(qū)時(shí)才需要同步鎖定
- 虛擬機(jī)是否使用TLAB,可以通過(guò)-XX:+/-UseTLAB參數(shù)來(lái)設(shè)定。
2、空閑列表
如果Java堆中的內(nèi)存并不是規(guī)整的,已被使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò)在一起,那就沒有辦法簡(jiǎn)單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄。
3、怎么選擇分配方式
兩種方式的選擇由 Java 堆是否規(guī)整決定,Java 堆是否規(guī)整是由選擇的垃圾收集器是否具有壓縮整理能力決定的。
①將內(nèi)存空間初始化為零值
內(nèi)存分配完成之后,虛擬機(jī)必須將分配到的內(nèi)存空間(但不包括對(duì)象頭)都初始化為零值。零值初始化意思就是對(duì)對(duì)象的字段賦0值,或者null值,這也就解釋了為什么這些字段在不需要進(jìn)程初始化時(shí)候就能直接使用。
如果使用了TLAB的話,這一項(xiàng)工作也可以提前至TLAB分配時(shí)順便進(jìn)行。
②對(duì)對(duì)象進(jìn)行必要的設(shè)置
例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭中。
從虛擬機(jī)的視角來(lái)看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了。但是從Java程序的視角看來(lái),對(duì)象創(chuàng)建才剛剛開始——構(gòu)造函數(shù),即Class文件中的()方法還沒有執(zhí)行,所有的字段都為默認(rèn)的零值,對(duì)象需要的其他資源和狀態(tài)信息也還沒有按照預(yù)定的意圖構(gòu)造好。
③執(zhí)行實(shí)例的初始化方法init
init方法包含成員變量、構(gòu)造代碼塊的初始化,按照聲明的順序執(zhí)行,執(zhí)行對(duì)象的構(gòu)造方法,并把堆內(nèi)對(duì)象的首地址賦值給引用變量。至此,對(duì)象創(chuàng)建成功。

三、注意事項(xiàng)
并發(fā)情況下,需要考慮操作的步驟是不是原子性,如果不是,就要加鎖。
原子性就是動(dòng)作不能再繼續(xù)被拆分了,讀是原子性,寫也是原子性,但是讀加上寫就不是原子性。
四、總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
JavaWeb實(shí)戰(zhàn)之用Servlet+JDBC實(shí)現(xiàn)用戶登錄與注冊(cè)
這篇文章主要介紹了JavaWeb實(shí)戰(zhàn)之用Servlet+JDBC實(shí)現(xiàn)用戶登錄與注冊(cè),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很大的幫助,需要的朋友可以參考下2021-04-04
在Java中實(shí)現(xiàn)可見性(visibility)的主要方法詳解
這篇文章主要介紹了在Java中實(shí)現(xiàn)可見性(visibility)的主要方法詳解,在Java中,使用關(guān)鍵字volatile和使用鎖(如synchronized關(guān)鍵字或 java.util.concurrent包中的鎖)來(lái)確保對(duì)共享變量的修改在多線程環(huán)境中能夠正確地被其他線程所觀察到,需要的朋友可以參考下2023-08-08
Java正則表達(dá)式處理特殊字符轉(zhuǎn)義的方法
由于正則表達(dá)式定了一些特殊字符,而有時(shí)候需要對(duì)這些特殊字符進(jìn)行匹配的話就需要進(jìn)行轉(zhuǎn)義了,下面這篇文章主要給大家介紹了Java正則表達(dá)式處理特殊字符轉(zhuǎn)義的方法,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-01-01
Java?Mybatis的初始化之Mapper.xml映射文件的詳解
這篇文章主要介紹了Java?Mybatis的初始化之Mapper.xml映射文件的詳解,解析完全局配置文件后接下來(lái)就是解析Mapper文件了,它是通過(guò)XMLMapperBuilder來(lái)進(jìn)行解析的2022-08-08
Spring?Boot?整合?Fisco?Bcos的案例分析(區(qū)塊鏈)
本篇文章介紹的?Spring?Boot?整合?Fisco?Bcos的案例,是在阿里云服務(wù)器上部署驗(yàn)證的。大家可根據(jù)自己的電腦環(huán)境,對(duì)比該案例進(jìn)行開發(fā)即可,具體案例代碼跟隨小編一起看看吧2022-01-01
SPRINGMVC JSON數(shù)據(jù)交互如何實(shí)現(xiàn)
這篇文章主要介紹了SPRINGMVC JSON數(shù)據(jù)交互如何實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Spring Boot利用@Async異步調(diào)用:使用Future及定義超時(shí)詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot利用@Async異步調(diào)用:使用Future及定義超時(shí)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2018-05-05

