摘要:堆內(nèi)存用于存放我們在程序中創(chuàng)建的對象,一旦沒有足夠的空間用于存放這些對象,即會拋出異常。當(dāng)我們采用后一種方式時,我們需要了解一個對象是如何占據(jù)堆內(nèi)存空間的,或者說是了解一個對象是由哪些部分組成的。
JVM將內(nèi)存劃分為程序計數(shù)器(Program Counter Register)、虛擬機(jī)棧(VM Stack)、本地方法棧(Native Method Stack)、堆(Heap)以及方法區(qū)(Method Area)。作為開發(fā)者,我們最關(guān)注的是虛擬機(jī)棧以及堆這兩塊區(qū)域。虛擬機(jī)棧所需要的內(nèi)存空間在編譯期間即可明確,而堆內(nèi)存所需要的空間需要在運行時才可確定。堆內(nèi)存用于存放我們在程序中創(chuàng)建的對象,一旦沒有足夠的空間用于存放這些對象,即會拋出OutOfMemoryError異常。在這種情況下,我們可以調(diào)整堆內(nèi)存的大小,或者對程序進(jìn)行優(yōu)化。當(dāng)我們采用后一種方式時,我們需要了解一個對象是如何占據(jù)堆內(nèi)存空間的,或者說是了解一個對象是由哪些部分組成的。
對象的內(nèi)存布局在HotSpot虛擬機(jī)中,對象在內(nèi)存中的布局劃分為3個區(qū)域:對象頭(Header),實例數(shù)據(jù)(Instance Data)以及對齊填充(Padding)。
對象頭HotSpot虛擬機(jī)對象的對象頭一般包含兩部分信息,第一部分用于存儲對象自身的運行時數(shù)據(jù),例如HashCode、GC分代年齡等信息。在32位和64位的JVM中,這部分?jǐn)?shù)據(jù)分別為32bit和64bit,官方稱這部分?jǐn)?shù)據(jù)為Mark Word。
另一部分用于存儲對象的類型指針,該指針指向它的類元數(shù)據(jù),JVM通過這個指針確定對象是哪個類的實例。在32位JVM中,指針的長度為32bit,在未開啟壓縮指針的64位JVM中,該指針的長度為64bit,如果開啟壓縮指針,那么為32bit。
之前提到對象頭一般包含兩部分信息,這是因為如果對象是一個數(shù)組,那么對象頭還需要有額外的空間用于存儲數(shù)組的長度,并且這部分?jǐn)?shù)據(jù)也隨著JVM位數(shù)的不同而不同:32位的JVM上,該區(qū)域的長度為32bit,在64位未開啟壓縮指針的JVM中,這部分?jǐn)?shù)據(jù)的長度為64bit,否則為32bit。
實例數(shù)據(jù)實例數(shù)據(jù)部分是對象真正存儲有效信息的區(qū)域,存儲了代碼中定義的各種字段的內(nèi)容,包括從父類繼承下來的字段和子類中定義的字段。
實例數(shù)據(jù)緊隨對象頭,為了提高存儲空間的利用率,這部分?jǐn)?shù)據(jù)的存儲順序會受到虛擬機(jī)分配策略參數(shù)和字段在Java源碼中定義順序的影響。HotSpot虛擬機(jī)默認(rèn)的分配策略如下所示。
doubles & longs
ints & floats
shorts & chars
booleans & bytes
references
可以看出,相同寬度的字段總是被分配到一起,并且在滿足這個條件的前提下,在父類中定義的字段會出現(xiàn)在子類字段之前。
對齊填充對齊填充這部分不是必須存在的,這部分僅僅是起著占位符的作用。由于HotSpot虛擬機(jī)的自動內(nèi)存管理系統(tǒng)要求對象的起始地址必須是8字節(jié)的整數(shù)倍,因此當(dāng)對象實例部分?jǐn)?shù)據(jù)沒有對齊時,就需要對剩余的部分進(jìn)行填充。
度量工具從JDK 5開始, Java提供了Instrumentation API,通過getObjectSize方法來獲取對象的大小,但是getObjectSize方法存在如下兩個缺陷,不能準(zhǔn)確的計算對象的大小。
不能直接調(diào)用getObjectSize方法,而是需要通過-javaagent參數(shù)指定一個特定的jar文件(包含Instrumentation代理)來啟動Instrumentation的代理程序。
如果一個對象中包含別的對象的引用,那么getObjectSize方法僅僅計算引用的大小,而不包括引用所指向的對象的大小。
由于上述兩個缺陷,我們不能直接調(diào)用getObjectSize方法來計算對象的大小,但是利用Java的反射機(jī)制,我們可以完整的計算一個對象的大小。我們解析對象的每一個Field(使用getDeclaredFields),并遵從如下規(guī)則。
當(dāng)Field是基本數(shù)據(jù)類型時,我們不再計算該Field的大小,因為該Field的大小已經(jīng)被包含在getObjectSize方法的返回值中。
當(dāng)Field是靜態(tài)數(shù)據(jù)或者是常量池中包含的數(shù)據(jù),那么我們忽略這些數(shù)據(jù),因為這些數(shù)據(jù)并不是屬于對象的。
我們需要保存我們已經(jīng)計算過的對象的引用,防止重復(fù)計算。
如果對象所屬的類存在父類,還需要計算父類中成員變量的大小。
jvm-obj-size 是以上思想的具體實現(xiàn),jvm-obj-size 實現(xiàn)了基本的獲取對象本身的大?。?b>sizeOf,僅包含引用本身),以及獲取對象真正的大小(fullSizeOf,包含引用所指向的對象)的方法,具體用法以及測試代碼詳見README文件。
參考深入理解Java虛擬機(jī)
Again about determining size of Java object
Java SE 6 新特性: Instrumentation 新功能
Java對象大小內(nèi)幕淺析
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/66290.html
摘要:再附一部分架構(gòu)面試視頻講解本文已被開源項目學(xué)習(xí)筆記總結(jié)移動架構(gòu)視頻大廠面試真題項目實戰(zhàn)源碼收錄 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava異常詳解Java抽象類和接口的區(qū)別Java深拷貝和淺拷...
摘要:前言本文內(nèi)容基本摘抄自深入理解虛擬機(jī),以供復(fù)習(xí)之用,沒有多少參考價值。此區(qū)域是唯一一個在虛擬機(jī)規(guī)范中沒有規(guī)定任何情況的區(qū)域。堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。虛擬機(jī)上把方法區(qū)稱為永久代。 前言 本文內(nèi)容基本摘抄自《深入理解Java虛擬機(jī)》,以供復(fù)習(xí)之用,沒有多少參考價值。想要更詳細(xì)了解請參考原書。 第二章 1.運行時數(shù)據(jù)區(qū)域 showImg(https://segment...
摘要:通過分析源碼,不難發(fā)現(xiàn),主要是通過循環(huán)解析文件并將信息解析到內(nèi)存對象,布局文件中定義的一個個組件都被順序的解析到了內(nèi)存中并被父子的形式組織起來,這樣通過給定的一個就可以將整個布局文件中定義的組件全部解析。 目錄介紹 01.前沿介紹 02.handleLaunchActivity 03.performLaunchActivity 04.activity.attach 05.Activi...
閱讀 2791·2021-09-22 15:58
閱讀 2319·2019-08-29 16:06
閱讀 983·2019-08-29 14:14
閱讀 2881·2019-08-29 13:48
閱讀 2519·2019-08-28 18:01
閱讀 1630·2019-08-28 17:52
閱讀 3400·2019-08-26 14:05
閱讀 1726·2019-08-26 13:50