JVM系列整體欄目
內(nèi)容 | 鏈接地址 |
---|---|
【一】初識(shí)虛擬機(jī)與java虛擬機(jī) | https://blog.csdn.net/zhenghuishengq/article/details/129544460 |
【二】jvm的類加載子系統(tǒng)以及jclasslib的基本使用 | https://blog.csdn.net/zhenghuishengq/article/details/129610963 |
【三】運(yùn)行時(shí)私有區(qū)域之虛擬機(jī)棧、程序計(jì)數(shù)器、本地方法棧 | https://blog.csdn.net/zhenghuishengq/article/details/129684076 |
【四】運(yùn)行時(shí)數(shù)據(jù)區(qū)共享區(qū)域之堆、逃逸分析 | https://blog.csdn.net/zhenghuishengq/article/details/129796509 |
【五】運(yùn)行時(shí)數(shù)據(jù)區(qū)共享區(qū)域之方法區(qū)、常量池 | https://blog.csdn.net/zhenghuishengq/article/details/129958466 |
【六】對(duì)象實(shí)例化、內(nèi)存布局和訪問定位 | https://blog.csdn.net/zhenghuishengq/article/details/130057210 |
一,對(duì)象實(shí)例化、內(nèi)存布局和訪問定位
1,對(duì)象的實(shí)例化
創(chuàng)建對(duì)象的方式和創(chuàng)建對(duì)象的步驟主要有以下幾種方式
1.1,創(chuàng)建對(duì)象的幾種方式
在日常開發(fā)中,創(chuàng)建對(duì)象的方式主要有以下幾種:
- 最常見的方式:new 加構(gòu)造器,如果構(gòu)造器私有,可以通過靜態(tài)訪問,如單例模式,或者通過工廠加載
//new 構(gòu)造器 創(chuàng)建對(duì)象
Object object = new Object();
//構(gòu)造器靜態(tài)私有,如典型的單例模式
Object object = Object.getObject();
//工廠加載,SpringBean,SqlSessionBean
Object object = ObjectFactory.getObject();
- 反射的方式:類的newInstance或者構(gòu)造器的newInstance·
public class Invoke {
public static void main(String[] args) {
try {
Class<?> clazz1 = Class.forName("com.tky.jvm.Invoke");
//通過類構(gòu)造器獲取對(duì)象
Constructor<?> constructor = clazz1.getConstructor();
Invoke invoke1 = (Invoke)constructor.newInstance();
//通過類名獲取
Class<Invoke> clazz2 = Invoke.class;
Invoke invoke2 = clazz2.newInstance();
//通過對(duì)象獲取
Invoke in = new Invoke();
Class<? extends Invoke> clazz3 = in.getClass();
Invoke invoke3 = clazz3.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 克隆的方式:clone的方式,不調(diào)用任何構(gòu)造器,當(dāng)前類需要實(shí)現(xiàn)Cloneable接口以及clone方法
/**
* @author zhenghuisheng
* @date : 2023/4/10
*/
@Data
public class Clone implements Cloneable {
private Long id;
private String username;
private String password;
@Override
protected Clone clone() throws CloneNotSupportedException {
return (Clone)super.clone();
}
}
class TestClone{
public static void main(String[] args) {
Clone clone1 = new Clone();
clone1.setId(1L);
clone1.setUsername("zhenghuisheng");
clone1.setUsername("123456");
try {
Clone clone2 = clone1.clone();
System.out.println(clone2.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 反序列化的方式:從文件或者網(wǎng)絡(luò)中獲取二進(jìn)制流,將二進(jìn)制流轉(zhuǎn)換成對(duì)象
//對(duì)象序列化
Student s = new Student("1","zhenghuisheng","18");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));
objectOutputStream.writeObject(s);
objectOutputStream.close();
//對(duì)象反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
Student student = (Student) inputStream.readObject();
- 第三方庫(kù)Objenesis
//構(gòu)建 Objenesis 對(duì)象 Objenesis需要對(duì)應(yīng)的pom依賴對(duì)象
Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator<Student> instantiator = objenesis.getInstantiatorOf(Student.class);
Student student = instantiator.newInstance();
1.2,對(duì)象創(chuàng)建的步驟
這里主要從執(zhí)行的角度來分析這個(gè)對(duì)象創(chuàng)建的步驟,如上圖所示,主要分為六個(gè)步驟來創(chuàng)建對(duì)象
1.2.1,判斷對(duì)象對(duì)應(yīng)的類是否加載,驗(yàn)證,準(zhǔn)備,解析和初始化
虛擬機(jī)在遇到一條new指令的時(shí)候,首先會(huì)去檢查這個(gè)指令的參數(shù)是否能在元空間的常量池中定位到一個(gè)類的符號(hào),并且檢查這個(gè)類是否經(jīng)歷過了加載、驗(yàn)證、準(zhǔn)備、解析和初始化這個(gè)幾個(gè)步驟。如果沒有,那么類加載器還在雙親委派的模式下,使用當(dāng)前類加載器的以 ClassLoader + package + class
為key進(jìn)行查找對(duì)應(yīng)的.class文件,如果沒有找到對(duì)應(yīng)的文件,則會(huì)拋出 ClassNotFoundException
異常,如果找到,則進(jìn)行類加載,并生成對(duì)應(yīng)的Class對(duì)象
1.2.2,為對(duì)象開辟空間,分配內(nèi)存
首先需要計(jì)算對(duì)象占用空間的大小,接著在堆中劃分一塊內(nèi)存給新對(duì)象,如果實(shí)例成員變量時(shí)引用變量,那么僅分配引用變量空間即可,即四個(gè)字節(jié)大小。如根據(jù)不同的基本數(shù)據(jù)類型其所占用的字節(jié)數(shù),從而得知每個(gè)變量占多大的空間,最后將這些變量所需要的空間全部疊加在一起,得到的就是這個(gè)總空間的字節(jié)數(shù)。
而內(nèi)存如果是規(guī)整的,那么虛擬機(jī)將采用的是 指針碰撞 的方式來為對(duì)象分配內(nèi)存。如下圖,就是將用過的內(nèi)存放在一邊,空閑的內(nèi)存放在另外一邊,中間放著一個(gè)分界點(diǎn)的指示器,內(nèi)存分配就是將指針向空閑那邊挪動(dòng),挪動(dòng)的距離就是對(duì)象所需要的大小 ,而指針碰撞這種方式,取決于虛擬機(jī)的垃圾回收算法是否具有壓縮功能。
如果內(nèi)存內(nèi)部不是規(guī)整的,虛擬機(jī)內(nèi)部就得維護(hù)一個(gè)列表來管理已使用的內(nèi)存和未使用的內(nèi)存,其方式被稱為 空閑列表 。如下圖,虛擬機(jī)內(nèi)部維護(hù)了一張表,記錄的是哪塊內(nèi)存時(shí)可用的,哪塊內(nèi)存時(shí)不可用的,然后在分配的時(shí)候,就從列表中找到一塊足夠打的空間劃分給對(duì)象實(shí)例,并更新表上的內(nèi)容。
1.2.3,處理并發(fā)問題
由于對(duì)象是在堆中創(chuàng)建,而堆又是共享區(qū)域,因此避免不了會(huì)出現(xiàn)這個(gè)并發(fā)的問題,而在堆內(nèi)部,主要采用了兩種方式來保證實(shí)例的安全性。
一種是采用CAS比較與交換的方式,失敗則重試,區(qū)域加鎖來保證更新的原子性,另一種是 每個(gè)線程預(yù)先分配一個(gè) TLAB。主要是通過這兩種方式來解決并發(fā)安全的問題。
1.2.4,對(duì)象初始賦值
這里進(jìn)行一個(gè)默認(rèn)的初始化。這樣所有屬性都有一個(gè)默認(rèn)值,保證對(duì)象實(shí)例字段在不賦值時(shí)就可以使用。因此在方法內(nèi)部,靜態(tài)變量在準(zhǔn)備階段就進(jìn)行了初始賦值,實(shí)例變量在分配空間的時(shí)候也進(jìn)行了初始賦值,因此這兩個(gè)可以直接使用變量,其他的變量如果沒有進(jìn)行顯示的初始化,那么會(huì)出現(xiàn)直接編譯失敗的情況。
1.2.5,設(shè)置對(duì)象的對(duì)象頭
將對(duì)象所屬的類、對(duì)象的hashCode、GC信息、年齡、鎖信息等存儲(chǔ)在對(duì)象的對(duì)象頭中。
1.2.6,執(zhí)行init方法進(jìn)行初始化
這里就行一個(gè)顯示初始化,初始化工作才正式開始。初始化成員變量,執(zhí)行實(shí)例化代碼塊,調(diào)用類的構(gòu)造方法,并把堆對(duì)象的首地址賦值給引用對(duì)象。因此一般來說,new 指令之后會(huì)接著就是執(zhí)行方法,將對(duì)象按照程序員的意愿進(jìn)行初始化,這樣真正可用的對(duì)象才算完整的創(chuàng)建出來。
2,對(duì)象的內(nèi)存布局
對(duì)象的內(nèi)存布局中,主要包括對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)其填充
2.1,對(duì)象頭(Header)
在對(duì)象頭中,又可以分為兩部分,一部分是運(yùn)行時(shí)的元數(shù)據(jù),另一部分就是類型指針。
運(yùn)行時(shí)元數(shù)據(jù)包括哈希碼、GC年齡分代、線程持有的鎖、持有鎖標(biāo)志、線程id、線程時(shí)間戳。
由下圖可知,在對(duì)象的年齡分代為4bit,因此最大為1111,即15,又由于是從0開始,因此其最大年齡為15,所以在設(shè)置這個(gè)年齡的時(shí)候,只能往小設(shè)置。鎖的標(biāo)志位對(duì)應(yīng)的字節(jié)碼用01、10、11表示,其值分別對(duì)應(yīng)著1、2、3,并且這段鎖升級(jí)的過程是不可逆的。
而類型指針指向的是元數(shù)據(jù)InstanceKlass,確定該對(duì)象所屬的類型。如果是數(shù)組,還需要記錄數(shù)組的長(zhǎng)度
2.2,實(shí)例數(shù)據(jù)(Instance Data)
對(duì)象真正存儲(chǔ)的有效信息,包括代碼中定義的各種類型的字段,以及父類繼承下來的和本身?yè)碛械淖侄?/strong> 。并且在這些對(duì)象中,父類定義的變量會(huì)出現(xiàn)在子類之前,并且相同的字段總是會(huì)被分配在一起,如果CompactFields參數(shù)為true,子類的窄變量可能插入到父類變量的空隙。
2.3,代碼示例
接下來分析一下以下這段代碼
/**
* @author zhenghuisheng
* @date : 2023/4/7
*/
public class Customer {
Integer id = 1001;
String name = "zhenghuisheng";
public Customer(){
Account account = new Account();
}
}
public class Test{
public static void main(String[] args){
Customer cust = new Customer();
}
}
然后其對(duì)應(yīng)的內(nèi)存結(jié)構(gòu)如下圖所示,在這個(gè)main方法中,由于是靜態(tài)方法,因此局部變量表的第一個(gè)slot不是this,而局部變量表中的第二個(gè)cust是引用著堆中 new Customer()的實(shí)例地址,該實(shí)例對(duì)象中,主要就是上面的運(yùn)行時(shí)元數(shù)據(jù)、類型指針、對(duì)其填充等組成。運(yùn)行時(shí)數(shù)據(jù)區(qū)就包括唯一地址哈希值、結(jié)果多次GC后的年齡、是否獲得鎖等標(biāo)志;類型指針對(duì)應(yīng)的就是Customer的Klass類元信息;實(shí)例數(shù)據(jù)就包括自身的屬性以及父類屬性
3,對(duì)象的訪問定位
創(chuàng)建對(duì)象主要是為了更好的去使用他,JVM內(nèi)部主要是通過兩種方式實(shí)現(xiàn)對(duì)象引用訪問到內(nèi)部對(duì)象的,一種是直接指針,一種是句柄訪問。
如下代碼所示,一個(gè)創(chuàng)建對(duì)象需要涉及到堆,棧和方法區(qū),因此對(duì)象的定位以及訪問也需要設(shè)計(jì)這三個(gè)地方
//第一個(gè)User存在方法區(qū),主要是存儲(chǔ)類信息和運(yùn)行時(shí)常量池
//第二個(gè)user在棧中,作為變量存儲(chǔ)
//最后的 new User存儲(chǔ)在堆中
User user = new User();
句柄訪問 的方式如下,在Java堆中有一個(gè)句柄池,然后句柄池中保存指向堆中的實(shí)例的地址和指向方法區(qū)中保存類信息的地址,而在棧中只需保存句柄池的地址即可。
直接指針 就是不需要使用句柄池,在棧的局部變量表中直接保存堆中實(shí)例的地址,而在堆中會(huì)有一個(gè)指針去指向方法區(qū)中保留類信息的地址。在Hotspot虛擬機(jī)中,主要采用的是這種方式
句柄指針需要在堆空間中開辟空間存儲(chǔ)句柄池,因此會(huì)有一定的空間浪費(fèi),并且效率相對(duì)較低,但是如果出現(xiàn)對(duì)象的位置發(fā)生改變,如出現(xiàn)垃圾回收的情況,或者使用標(biāo)記整理算法的時(shí)候 ,這個(gè)棧中指向堆中的句柄池的指針可以不用發(fā)生改變,只需改變句柄池只向?qū)嵗龜?shù)據(jù)和方法區(qū)的指針。而這個(gè)直接指針的優(yōu)缺點(diǎn)就是就和句柄指針相反。
4,直接內(nèi)存初體驗(yàn)(了解)
在JDK8中,方法區(qū)的具體實(shí)現(xiàn)從永久代變成了元空間,而元空間使用的是本地內(nèi)存,又名直接內(nèi)存,這部分不屬于運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《java虛擬機(jī)規(guī)范》中定義的內(nèi)存區(qū)域
在java代碼中,可以直接通過這個(gè) ByteBuffer.allocateDirect()
來進(jìn)行本地內(nèi)存空間的分配, 即直接通過這個(gè)NIO來進(jìn)行操作,并且在通常直接內(nèi)存的速度會(huì)直接優(yōu)于Java堆,其讀寫性能相對(duì)較高。因此處于性能考慮,讀寫頻繁的場(chǎng)合可以使用直接內(nèi)存,并且Java的NIO庫(kù),也允許Java程序使用直接內(nèi)存。
也可能會(huì)出現(xiàn) OutOfMemoryError
異常,由于直接內(nèi)存在Java堆之外,因此其大小不會(huì)受限于 -Xmx 指定的最大堆大小,但又由于系統(tǒng)的內(nèi)存始終是有限的,因此堆和直接內(nèi)存的總和依然受限于操作系統(tǒng)給出的最大內(nèi)存,但是在直接內(nèi)存中,也存在一定的缺點(diǎn):分配回收成本較高,并且不受JVM內(nèi)存回收管理。文章來源:http://www.zghlxwxcb.cn/news/detail-415398.html
因此可以直接內(nèi)存可以通過 MaxDirectMemorySize
進(jìn)行大小的設(shè)置,如果未指定,那么默認(rèn)和堆的最大值 -Xmx
參數(shù)值一致文章來源地址http://www.zghlxwxcb.cn/news/detail-415398.html
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100 * 1024);
到了這里,關(guān)于【jvm系列-06】深入理解對(duì)象的實(shí)例化、內(nèi)存布局和訪問定位的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!