面試官欺負人:new?Object()到底占用幾個字節(jié)?
時間:2021-10-29 13:55:45
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]前言我們來分析一下堆內(nèi)布局以及Java對象在內(nèi)存中的布局吧。對象的指向先來看一段代碼:package?com.zwx.jvm;public?class?HeapMemory?{????private?Object?obj1?=?new?Object();????public?st...
前言
我們來分析一下堆內(nèi)布局以及Java對象在內(nèi)存中的布局吧。
對象的指向
先來看一段代碼:
public class HeapMemory {
private Object obj1 = new Object();
public static void main(String[] args) {
Object obj2 = new Object();
}
}
123456789 上面的代碼中,obj1 和obj2在內(nèi)存中有什么區(qū)別?
Java內(nèi)存模型
對象內(nèi)存中可以分為三塊區(qū)域:對象頭(Header),實例數(shù)據(jù)(Instance Data)和對齊填充(Padding),以64位操作系統(tǒng)為例(未開啟指針壓縮的情況)Java對象布局如下圖所示:其中對象頭中的Mark Word中的詳細信息在文章synchronized鎖升級原理中有詳細介紹。上圖中的對齊填充不是一定有的,如果對象頭和實例數(shù)據(jù)加起來剛好是8字節(jié)的倍數(shù),那么就不需要對齊填充。
Object obj=new Object()占用字節(jié)
這是網(wǎng)上很多人都會提到的一個問題,那么結(jié)合上面的Java內(nèi)存布局,我們來分析下,以64位操作系統(tǒng)為例,new Object()占用大小分為兩種情況:
- 未開啟指針壓縮 占用大小為:8(Mark Word) 8(Class Pointer)=16字節(jié)
- 開啟了指針壓縮(默認是開啟的) 開啟指針壓縮后,Class Pointer會被壓縮為4字節(jié),最終大小為:8(Mark Word) 4(Class Pointer) 4(對齊填充)=16字節(jié)
org.openjdk.jol
jol-core
0.10
12345 然后新建一個簡單的demo:
import org.openjdk.jol.info.ClassLayout;
public class HeapMemory {
public static void main(String[] args) {
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
12345678910 輸出結(jié)果如下:最后的結(jié)果是16字節(jié),沒有問題,這是因為默認開啟了指針壓縮,那我們現(xiàn)在把指針壓縮關閉之后再去試試。
-XX:-UseCompressedOops 關閉指針壓縮
12 再次運行,得到如下結(jié)果:可以看到,這時候已經(jīng)沒有了對齊填充部分了,但是占用大小還是16位。
public class MyItem {
byte i = 0;
}
12345 然后分別在開啟指針壓縮和關閉指針壓縮的場景下分別輸出這個類的大小。
import org.openjdk.jol.info.ClassLayout;
public class HeapMemory {
public static void main(String[] args) {
MyItem myItem = new MyItem();
System.out.println(ClassLayout.parseInstance(myItem).toPrintable());
}
}
12345678910 開啟指針壓縮,占用16字節(jié):關閉指針壓縮,占用24字節(jié):這個時候就能看出來開啟了指針壓縮的優(yōu)勢了,如果不斷創(chuàng)建大量對象,指針壓縮對性能還是有一定優(yōu)化的。
對象的訪問
創(chuàng)建好一個對象之后,當然需要去訪問它,那么當我們需要訪問一個對象的時候,是如何定位到對象的呢?目前最主流的訪問對象方式有兩種:句柄訪問和直接指針訪問。
- 句柄訪問 使用句柄訪問的話,Java虛擬機會在堆內(nèi)劃分出一塊內(nèi)存來存儲句柄池,那么對象當中存儲的就是句柄地址,然后句柄池中才會存儲對象實例數(shù)據(jù)和對象類型數(shù)據(jù)地址。
- 直接指針訪問(Hot Spot虛擬機采用的方式) 直接指針訪問的話對象中就會直接存儲對象類型數(shù)據(jù)。
句柄訪問和直接指針訪問對比
上面圖形中我們很容易對比,就是如果使用句柄訪問的時候,會多了一次指針定位,但是他也有一個好處就是,假如一個對象被移動(地址改變了),那么只需要改變句柄池的指向就可以了,不需要修改reference對象內(nèi)的指向,而如果使用直接指針訪問,就還需要到局部變量表內(nèi)修改reference指向。
堆內(nèi)存
上面我們提到,在Java對象頭當中的Mark Word存儲了對象的分代年齡,那么什么是分代年齡呢?
Young區(qū)
現(xiàn)在拆分成了Young區(qū),那我們看下面一個場景,下面的Young是經(jīng)過垃圾回收之后的一個概圖:假如說現(xiàn)在來了一個對象,要占用2個對象的大小,會發(fā)現(xiàn)放不下去了,這時候就會觸發(fā)GC(垃圾回收),但是一旦觸發(fā)了GC(垃圾回收),對用戶線程是有影響的,因為GC過程中為了確保對象引用不會不斷變化,需要停止所有用戶線程,Sun把這個事件稱之為:Stop the World(STW)。這些在下一篇講解垃圾回收的時候會詳細介紹,這里先不深入。
Old區(qū)
當Young區(qū)的對象達到設置的分代年齡之后,對象會進入Old區(qū),Old區(qū)滿了之后會觸發(fā)Full GC,如果還是清理不掉空間,那么就拋出OutOfMemeoyError異常。
名詞掃盲
上面提到了很多新的名詞,而實際上很多這種名詞還有其他叫法,這個還是覺得有必要了解一下。
- 垃圾回收:簡稱GC。
- Minor GC:針對新生代的GC
- Major GC:針對老年代的GC,一般老年代觸發(fā)GC的同時也會觸發(fā)Minor GC,也就等于觸發(fā)了Full GC。
- Full GC:新生代 老年代同時發(fā)生GC。
- Young區(qū):新生代
- Old區(qū):老年代
- Eden區(qū):暫時沒發(fā)現(xiàn)有什么中文翻譯(伊甸園?)
- Surcivor區(qū):幸存區(qū)
- S0和S1:也稱之為from區(qū)和to區(qū),注意from和to兩個區(qū)是不斷互換身份的,且S0和S1一定要相等,并且保證一塊區(qū)域是空的
一個對象的人生軌跡圖
從上面的介紹大家應該有一個大致的印象,一個對象會在Eden區(qū),S0區(qū),S1區(qū),Old區(qū)不斷流轉(zhuǎn)(當然,一開始就會被回收的短命對象除外),我們可以得到下面的一個流程圖:
總結(jié)
本文主要介紹了一個Java對象在堆內(nèi)是如何存儲的,并結(jié)合Java對象的內(nèi)存布局示范了一個普通對象占用大小問題,然后還分析了堆內(nèi)的空間劃分以及劃分原因,本文中涉及到了GC相關知識均沒有深入講解,關于GC及GC算法和GC收集器等相關知識將放在下一篇進行詳細分析。