1. 解釋一下對象的創建過程
“半初始化”狀態通常指的是對象在內存分配后、但在完全初始化之前的一種狀態。在Java中,雖然JVM的規范和設計努力避免對象處于這種不穩定的狀態,但在多線程環境下,由于指令重排序等并發問題,仍有可能出現對象半初始化的現象。
具體來說,如果JVM在分配了對象的內存空間后,尚未完成構造方法的執行(即對象還未完全初始化),而另一個線程已經獲得了這個對象的引用,那么這個對象就處于“半初始化”狀態。此時,如果嘗試訪問對象的屬性或方法,可能會得到不一致或錯誤的結果。
2. DCL要不要加volatile
在Java中,當涉及到雙重檢查鎖定(Double-Checked Locking, DCL)模式時,確實需要特別考慮線程安全問題,尤其是對象的延遲初始化。DCL模式允許你在多線程環境下延遲對象的初始化,同時減少同步的開銷。然而,如果不正確使用,它可能導致所謂的“半初始化”問題,即一個線程可能看到對象的引用但尚未看到其完全初始化的狀態。
為了避免這個問題,在Java中,當使用DCL模式時,確實需要將涉及的變量聲明為volatile。這是因為volatile關鍵字有兩個主要特性:
可見性:確保一個線程對變量的修改對其他線程是立即可見的。這防止了線程在讀取變量時可能看到的過時值(即緩存中的值)。
禁止指令重排序:在多線程環境中,編譯器和處理器可能會對指令進行重排序以優化性能。然而,這種重排序可能會破壞程序的語義。volatile關鍵字可以禁止這種重排序,特別是在涉及變量的讀寫操作時。
3. 對象在內存中的存儲布局
存儲布局:在Java堆中,對象按照其生命周期被劃分為不同的代(如年輕代、老年代等),但這主要影響垃圾收集的策略,而非對象本身的存儲布局。從對象結構的角度看,一個對象在堆中的存儲通常包括以下幾個部分:
- 對象頭(Object Header):包含對象的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等。此外,如果對象是一個數組的實例,那么對象頭中還需要記錄數組的長度。(對象頭又分為markword和classpointer)
- 實例數據(Instance Data):存放類的屬性信息,包括從父類繼承的屬性。這部分內存的分配受到JVM自動內存管理系統管理。
- 對齊填充(Padding):由于JVM要求對象起始地址必須是8的倍數,因此對象占用的內存空間必須是8的倍數。如果對象實例數據部分沒有對齊,就需要通過對齊填充來補全。
4. 對象頭具體包括什么?(Object Header)
- markword:鎖信息、哈希碼(HashCode)、GC回收信息
- classpointer:指向new出來對象的類
對象頭(Object Header):包含對象的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等。此外,如果對象是一個數組的實例,那么對象頭中還需要記錄數組的長度。
5.對象怎么定位?
對象定位主要有兩種方式:
- 句柄方式:
這種方式使用兩個指針來定位對象。第一個指針指向實際new出來的對象,第二個指針是指向類型數據的指針。這種方式在垃圾回收時相對穩定,因為即使對象在內存中移動,句柄也不會改變,只需更新句柄中指向對象的指針即可。
句柄方式的一個優點是垃圾回收的穩定性和效率,尤其是在使用如G1這樣的垃圾回收器時,它可以有效處理對象移動的情況。 - 直接指針方式:
直接指針方式通過直接存儲對象的內存地址來訪問對象,這種方式訪問速度通常較快。然而,在垃圾回收時,如果對象被移動,直接指針需要更新,這可能會導致效率問題。
因此,在需要頻繁垃圾回收的場景下,直接指針方式可能不如句柄方式穩定。
6.對象怎么分配?
在Java中,對象主要在堆(Heap)內存中分配空間。
堆內存是Java虛擬機(JVM)所管理的內存中最大的一塊,用于存放由new創建的對象和數組。堆內存還可以細分為新生代(Young Generation)、老年代(Old Generation)等區域
7. Object o = new Object()在內存中占用多少字節?
16個字節(8個字節markword,4個字節class pointer、0個字節成員變量,4個字節對齊)(對齊字節數需要被8整除)