Java基礎(chǔ)之創(chuàng)建虛擬機(jī)對(duì)象的過程詳細(xì)總結(jié)
一、對(duì)象的創(chuàng)建
1.1 new 類名
虛擬機(jī)遇到一條new指令時(shí),
首先檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載、解析和初始化過
。如果沒有,先執(zhí)行相應(yīng)的類加載過程
。
1.2 分配內(nèi)存
虛擬機(jī)為新生對(duì)象分配內(nèi)存。
對(duì)象所需內(nèi)存大小在類加載完成后就可以確定,為對(duì)象分配內(nèi)存等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來
。
(1)內(nèi)存分配的方式有兩種:
①
指針碰撞
: java堆如果規(guī)整,一邊是用過的內(nèi)存,一邊是空閑的內(nèi)存,中間一個(gè)指針作為邊界指示器; 分配內(nèi)存只需向空閑那邊移動(dòng)指針空出與對(duì)象大小相等的空間;②
空閑列表
: 如果不規(guī)整,即用過的和空閑的內(nèi)存相互交錯(cuò);則虛擬機(jī)需要維護(hù)一個(gè)列表,記錄哪些內(nèi)存可用;分配內(nèi)存時(shí)查表找到一個(gè)足夠大的內(nèi)存,并更新列表記錄。
選擇哪種分配方式是根據(jù)這個(gè)虛擬機(jī)所采用的垃圾收集器是否帶有壓縮整理功能決定的
:如果虛擬機(jī)的虛擬器帶壓縮整理功能,則系統(tǒng)采用指針碰撞的內(nèi)存分配算法;否則采用空閑列表的算法。
(2)線程安全問題
并發(fā)時(shí),上面兩種方式分配內(nèi)存的操作都不是線程安全的,有兩種解決方案:
①同步處理
JVM采用CAS(Compare and Swap)機(jī)制加上失敗重試的方式,保證更新操作的原子性;
CAS:有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做;
②本地線程分配緩沖區(qū)(TLAB)
把分配內(nèi)存的動(dòng)作按照線程劃分在不同的空間中進(jìn)行:每個(gè)線程在Java堆預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB);哪個(gè)線程需要分配內(nèi)存就從哪個(gè)線程的TLAB上分配;只有TLAB用完需要分配新的TLAB時(shí),才需要同步處理。
JVM通過"-XX:+/-UseTLAB"指定是否使用TLAB。
1.3 初始化零值
內(nèi)存分配完之后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值。如果用TLAB,則在TLAB分配時(shí)進(jìn)行。這
保證了程序中對(duì)象(及實(shí)例變量)不顯式初始賦零值,程序也能訪問到零值
。
1.4 設(shè)置對(duì)象信息
虛擬機(jī)對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、 如何才能找到類的元數(shù)據(jù)信息、 對(duì)象的哈希碼、 對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭(Object Header)之中。
1.5 構(gòu)造對(duì)象
執(zhí)行init方法,即按照程序員的意愿進(jìn)行初始化
。至此真正可用的對(duì)象才算完全被構(gòu)造出來。
二、對(duì)象的內(nèi)存布局
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:
對(duì)象頭
(Header)、實(shí)例數(shù)據(jù)
(InstanceData)和對(duì)齊填充
(Padding)。
2.1 對(duì)象頭
HotSpot虛擬機(jī)的對(duì)象頭包含兩部分:
(1)第一部分用于存儲(chǔ)對(duì)象自身運(yùn)行時(shí)數(shù)據(jù)
,這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)中分別為32bit和64bit,官方稱它為“Mark Word”。
(2)另外一部分是類型指針
,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是那個(gè)類的實(shí)例
。
并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類型指針,即查找對(duì)象的元數(shù)據(jù)信息并不一定要經(jīng)過對(duì)象本身。
另外,如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過普通Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小,但是從數(shù)組的元數(shù)據(jù)中卻無法確定數(shù)組的大小。
2.2 實(shí)例數(shù)據(jù)
實(shí)例數(shù)據(jù)部分是
對(duì)象真正存儲(chǔ)的有效信息
,也是在程序代碼中所定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來
。這部分的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)(FiedsAllocationStyle)和字段在Java源碼中定義順序的影響。
HotSpot虛擬機(jī)默認(rèn)的分配策略為:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)。從分配策略中可以看出,相同寬度的字段總是被分配到一起
。在滿足這個(gè)前提條件的情況下,在父類中定義的變量會(huì)出現(xiàn)在子類之前
。如果CompactFieds參數(shù)值為true(默認(rèn)為true),那么子類中較窄的變量也可能會(huì)插入到父類變量的空隙之中。
2.3 對(duì)齊填充
對(duì)齊填充并不是必然存在的,也沒有特別的含義,他僅僅
起占位符的作用
。由于HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,換句話說,就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍,而對(duì)象頭部分正好是8字節(jié)的倍數(shù),因此,當(dāng)對(duì)象實(shí)例部分沒有對(duì)齊時(shí),就需要通過對(duì)齊填充來補(bǔ)全
。
三、對(duì)象的訪問定位
建立對(duì)象是為了使用對(duì)象,Java程序通過棧上的reference數(shù)據(jù)來操作堆上的具體對(duì)象
。
reference類型在Java虛擬機(jī)規(guī)范中之規(guī)定了一個(gè)指向?qū)ο蟮囊?,但沒有定義這個(gè)引用應(yīng)該通過何種方式去定位訪問隊(duì)中的對(duì)象的具體位置,因此對(duì)象的訪問方式也是由虛擬機(jī)實(shí)現(xiàn)而定的。目前主流方式是使用句柄和直接指針兩種。
3.1 使用句柄
如果以句柄方式訪問,
Java堆中將會(huì)劃分出一塊內(nèi)存作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息
。
3.2 指針方式
如果以指針方式訪問,那么Java堆對(duì)象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,而
reference中存儲(chǔ)的直接就是對(duì)象地址
,如果只是訪問對(duì)象本身,就會(huì)少一次間接訪問的開銷。
四、兩種方式的比較
句柄訪問最大好處就是
reference中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要修改
。
指針訪問方式最大好處就是速度更快,節(jié)省了一次指針定位的時(shí)間開銷
,由于對(duì)于下部分的訪問在Java中非常頻繁,因此此類開銷積少成多后也是一項(xiàng)非常可觀的執(zhí)行成本。
到此這篇關(guān)于Java基礎(chǔ)之創(chuàng)建虛擬機(jī)對(duì)象的過程詳細(xì)總結(jié)的文章就介紹到這了,更多相關(guān)虛擬機(jī)中對(duì)象的創(chuàng)建過程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java虛擬機(jī)之類加載
- java虛擬機(jī)之JVM調(diào)優(yōu)詳解
- Java虛擬機(jī)內(nèi)存區(qū)域劃分詳解
- 概述java虛擬機(jī)中類的加載器及類加載過程
- 深入了解Java虛擬機(jī)棧以及內(nèi)存模型
- 淺析Java虛擬機(jī)詳解之概述、對(duì)象生存法則
- Java跨平臺(tái)原理與虛擬機(jī)相關(guān)簡(jiǎn)介
- java虛擬機(jī)jvm方法區(qū)實(shí)例講解
- java虛擬機(jī)鉤子關(guān)閉函數(shù)addShutdownHook的操作
- java虛擬機(jī)是做什么用的
- 詳解Java 虛擬機(jī)垃圾收集機(jī)制
- Java虛擬機(jī)使用jvisualvm工具遠(yuǎn)程監(jiān)控tomcat內(nèi)存
- Java虛擬機(jī)常見內(nèi)存溢出錯(cuò)誤匯總
- Java虛擬機(jī)執(zhí)行引擎知識(shí)總結(jié)
- Java啟用Azure Linux虛擬機(jī)診斷設(shè)置
- Java虛擬機(jī)內(nèi)存溢出與內(nèi)存泄漏
- java虛擬機(jī)創(chuàng)建失敗的原因整理
- Java內(nèi)存模型中的虛擬機(jī)棧原理分析
相關(guān)文章
使用springboot對(duì)外部靜態(tài)資源文件的處理操作
這篇文章主要介紹了使用springboot對(duì)外部靜態(tài)資源文件的處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08ReentrantLock條件變量使多個(gè)線程順序執(zhí)行
這篇文章主要為大家介紹了ReentrantLock條件變量使多個(gè)線程順序執(zhí)行,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java利用opencv實(shí)現(xiàn)用字符展示視頻或圖片的方法
這篇文章主要介紹了Java利用opencv實(shí)現(xiàn)用字符展示視頻或圖片的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Java中CyclicBarrier和CountDownLatch的用法與區(qū)別
CyclicBarrier和CountDownLatch這兩個(gè)工具都是在java.util.concurrent包下,并且平時(shí)很多場(chǎng)景都會(huì)使用到。本文將會(huì)對(duì)兩者進(jìn)行分析,記錄他們的用法和區(qū)別,感興趣的可以了解一下2021-08-08Java Collection 移除元素方法及注意事項(xiàng)
這篇文章主要介紹了Java Collection 移除元素方法及注意事項(xiàng),通過一個(gè)簡(jiǎn)單實(shí)例給大家講解,需要的朋友可以參考下2020-01-01Java幸運(yùn)28系統(tǒng)搭建數(shù)組的使用實(shí)例詳解
在本篇文章里小編給大家整理了關(guān)于Java幸運(yùn)28系統(tǒng)搭建數(shù)組的使用實(shí)例內(nèi)容,有需要的朋友們可以參考學(xué)習(xí)下。2019-09-09SpringBoot 自定義注解異步記錄復(fù)雜日志詳解
這篇文章主要為大家介紹了SpringBoot 自定義注解異步記錄復(fù)雜日志詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09