JVM_04 对象的实例化+内存布局+访问定位+直接内存
一、前言:
- (1).new 最常見的方式 | 變形1 : Xxx的靜態(tài)方法 | 變形2 : XxBuilder/XxoxFactory的靜態(tài)方法
- (2).Class的newInstance():反射的方式,只能調(diào)用空參的構(gòu)造器,權(quán)限必須是public
- (3).Constructor的newInstance(Xxx):反射的方式,可以調(diào)用空參、帶參的構(gòu)造器,權(quán)限沒有要求
- (4).使用clone() :不調(diào)用任何構(gòu)造器,當(dāng)前類需要實(shí)現(xiàn)Cloneable接口,實(shí)現(xiàn)clone()
- (5).使用反序列化:從文件中、從網(wǎng)絡(luò)中獲取一個(gè)對(duì)象的二進(jìn)制流
- (6).第三方庫Objenesis
二、 對(duì)象的實(shí)例化 掌握
判斷對(duì)象對(duì)應(yīng)的類是否加載、鏈接、初始化
(虛擬機(jī)遇到一條new指令,首先去檢查這個(gè)指令的參數(shù)能否在Metaspace的常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載、解析和初始化。( 即判斷類元信息是否存在)。如果沒有,那么在雙親委派模式下,使用當(dāng)前類加載器以ClassLoader+包名+類名為Key進(jìn)行查找對(duì)應(yīng)的.class文件。如果沒有找到文件,則拋出ClassNotFoundException異常,如果找到,則進(jìn)行類加載,并生成對(duì)應(yīng)的Class類對(duì)象)
為對(duì)象分配內(nèi)存:首先計(jì)算對(duì)象占用空間大小,接著在堆中劃分一塊內(nèi)存給新對(duì)象。 如果實(shí)例成員變量是引用變量,僅分配引用變量空間即可,即4個(gè)字節(jié)大小
(byte、int、float、引用數(shù)據(jù)類型4個(gè)字節(jié)大小 | double、long 占八個(gè)字節(jié))
- 如果內(nèi)存規(guī)整,使用指針碰撞
如果內(nèi)存是規(guī)整的,那么虛擬機(jī)將采用的是指針碰撞法(BumpThePointer)來為對(duì)象分配內(nèi)存。意思是所有用過的內(nèi)存在一邊,空閑的內(nèi)存在另外一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就僅僅是把指針向空閑那邊挪動(dòng)一段與對(duì)象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機(jī)采用這種分配方式。一般使用帶有compact
(整理)過程的收集器時(shí),使用指針碰撞。 如果內(nèi)存不規(guī)整,虛擬機(jī)需要維護(hù)一個(gè)列表,使用空閑列表分配(CMS) - 如果內(nèi)存不是規(guī)整的,已使用的內(nèi)存和未使用的內(nèi)存相互交錯(cuò),那么虛擬機(jī)將采用的是空閑列表法來為對(duì)象分配內(nèi)存。意思是虛擬機(jī)維護(hù)了一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,再分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的內(nèi)容。這種分配方式成為“空閑列表(Free List)
- 說明:選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
(在分配內(nèi)存空間時(shí),另外一個(gè)問題是及時(shí)保證new對(duì)象時(shí)候的線程安全性:創(chuàng)建對(duì)象是非常頻繁的操作,虛擬機(jī)需要解決并發(fā)問題。虛擬機(jī)采用了兩種方式解決并發(fā)問題:)
- CAS ( Compare And Swap )失敗重試、區(qū)域加鎖:保證指針更新操作的原子性
- TLAB把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖區(qū),(TLAB,Thread Local Allocation Buffer) 虛擬機(jī)是否使用TLAB,可以通過一XX:+/一UseTLAB參數(shù)來 設(shè)定
初始化分配到的空間:賦予默認(rèn)的初始化值;比如int=0| boolean=false(默認(rèn)的值)
設(shè)置對(duì)象的對(duì)象頭:將對(duì)象的所屬類(即類的元數(shù)據(jù)信息)、對(duì)象的HashCode和對(duì)象的GC信息、鎖信息等數(shù)據(jù)存儲(chǔ)在對(duì)象的對(duì)象頭中。這個(gè)過程的具體設(shè)置方式取決于JVM實(shí)現(xiàn)。
執(zhí)行init方法進(jìn)行初始化(進(jìn)行賦值的處理)
(在Java程序的視角看來,初始化才正式開始。初始化成員變量,執(zhí)行實(shí)例化代碼塊,調(diào)用類的構(gòu)造方法,并把堆內(nèi)對(duì)象的首地址賦值給引用變量。
因此一般來說(由字節(jié)碼中是否跟隨有invokespecial指令所決定),new指令之 后會(huì)接著就是執(zhí)行方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全創(chuàng)建出來。)
⑦. 代碼展示
/*** 測(cè)試對(duì)象實(shí)例化的過程* ① 加載類元信息 - ② 為對(duì)象分配內(nèi)存 - ③ 處理并發(fā)問題 - ④ 屬性的默認(rèn)初始化(零值初始化)* - ⑤ 設(shè)置對(duì)象頭的信息 - ⑥ 屬性的顯式初始化、代碼塊中初始化、構(gòu)造器中初始化** 給對(duì)象的屬性賦值的操作:* ① 屬性的默認(rèn)初始化 - ② 顯式初始化 / ③ 代碼塊中初始化 - ④ 構(gòu)造器中初始化* */ public class Customer{int id = 1001;String name;Account acct;{name = "匿名客戶";}public Customer(){acct = new Account();}}class Account{}三、對(duì)象的內(nèi)存布局
對(duì)象頭包含兩部分:(棧中的地址值就是一個(gè)哈希值) 掌握
- 運(yùn)行時(shí)元數(shù)據(jù) (哈希值( HashCode )、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳)
- 類型指針:指向類元數(shù)據(jù)的InstanceKlass,確定該對(duì)象所屬的類型
- 說明:如果是數(shù)組,還需記錄數(shù)組的長(zhǎng)度
- 說明:它是對(duì)象真正存儲(chǔ)的有效信息,包括程序代碼中定義的各種類型的字段(包括從父類繼承下來的和本身擁有的字段) 規(guī)則:
- 相同寬度的字段總被分配在一起
- 父類中定義的變量會(huì)出現(xiàn)在子類之前
- 如果CompactFields參數(shù)為true(默認(rèn)為true),子類的窄變量可能插入到父類變量的空隙
①. 不是必須的,也沒特別含義,僅僅起到占位符作用
②. 解釋如下圖:
①. 代碼演示
②. 圖解👆代碼
四、對(duì)象的訪問定位
前言:
JVM是如何通過棧幀中的對(duì)象引|用訪問到其內(nèi)部的對(duì)象實(shí)例的呢?-> 定位,通過棧上reference訪問
句柄訪問
直接指針(HotSpot采用)
五、直接內(nèi)存(Direct Memory) 了解
不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》中定義的內(nèi)存區(qū)域
直接內(nèi)存是Java堆外的、直接向系統(tǒng)申請(qǐng)的內(nèi)存區(qū)間
代碼演示:
通常,訪問直接內(nèi)存的速度會(huì)優(yōu)于Java堆。即讀寫性能高
直接內(nèi)存大小可以通過MaxDirectMemorySize設(shè)置,如果不指定,默認(rèn)與堆的最大值一Xmx參數(shù)值一致
⑦. 簡(jiǎn)單理解: java process memory = java heap + native memory
總結(jié)
以上是生活随笔為你收集整理的JVM_04 对象的实例化+内存布局+访问定位+直接内存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM_03 运行时数据区 [ 方法区]
- 下一篇: 产品经理没有做过成功的产品,该何去何从?