1.說一下對象創建的過程?對象有哪幾部分構成?虛擬機如何訪問對象?
(1)對象創建的過程
- 類加載檢查:在執行到new命令時,查看new後面的參數是否正確定位到常量池中的符號引用,並且該符號引用是否被正確的加載、連接和初始化。
- 內存分配:爲對象分配內存,有指針碰撞和空閒列表兩種方式。
- 指針碰撞:適用於內存完整,無碎片。內存使用和未使用中間由指針隔開,分配的時候指針移動相應的位置。
- 空閒列表:適用於內存不完整,有碎片。維護一個列表,列表記錄可用的內存,使用時分配給對象一個足夠的內存空間並更新列表
- 初始化零值:將對象分配到的空間初始化零值(不包括對象頭)
- 設置對象頭:對象頭信息包括哈希碼、GC分代信息、元數據信息、對象是哪個類的示例等
- 執行init方法:給對象設置程序值,執行構造方法。
(2)對象在內存中的組成部分(對象在內存的劃分、對象的內存佈局)
- 對象頭:
- 第一:元數據、GC分代、哈希值等自身運行信息;
- 第二:類型指針,確定屬於哪個類的實例
- 實例數據:對象真正存儲的有效信息,定義的各種字段的值。
- 對齊填充:沒有實際意義,JVM內存地址需要8字節的整數倍
(3)虛擬機如何訪問對象
- 句柄:堆中專門劃分一個區域作爲句柄池,虛擬機棧存儲是堆中句柄的地址,句柄存儲的是對象的實例數據和類型數據的地址
- 直接指針:棧中的引用直接指向的就是對象的實例數據和類型數據的地址。
對比:直接指針避免二次尋址;在對象移動時,只修改句柄地址而不懂改變引用的地址。
2.java 中都有哪些引用類型?
- 強引用:new 語句產生的都是強引用,虛擬機不會主動去回收,即便內存溢出,也是會拋出異常而不是回收強引用。可以將對象=null,從而在垃圾回收器在下一次回收。
- 軟引用:在內存足夠時,不會回收,在內存不足時,會回收。常用於緩存技術。
- 弱引用:垃圾回收器遇到該引用就會回收,常與引用隊列一起使用
- 虛引用:最弱的引用,在對象被JVM回收之後收到一個系統通知,用於追蹤垃圾回收過程,必須與應用隊列一起使用。
弱引用和虛引用很少用,軟引用使用比較多,用於加速JVM對垃圾內存的回收速度,維護系統安全,防止內存溢出
3.怎麼判斷對象是否可以被回收?
3.1對象標記算法:
- 引用計數法:引用+1,引用失效-1,對象引用爲0,則不可在使用;無法解決對象互相循環引用的問題。
- 可達性分析:選用某些對象作爲GC Roots節點,從Roots節點向下搜索,形成一個引用鏈,所有不在引用鏈上的對象都是不可使用的對象
可以作爲GC Roots的節點:
- 虛擬機棧引用對象
- 全局靜態變量
- 常量
- 本地棧引用對象
3.2判斷回收
需要進行二次標記,標記使用的是上面的算法
- 第一次標記:所有沒在GC Roots引用鏈上的對象都進行第一次標記,所有執行過finalize方法或者未覆蓋finalize方法的對象,直接回收;除此之外的標記過一次的對象都進入到F-Queue隊列中。
- 對F-Queue中的對象進行二次標記,如果在finalize方法中,對象又重新加入引用鏈,則不回收,否則就進行回收。
4.內存泄露和內存溢出分別是什麼?什麼原因造成?如何避免?
- 內存泄露:本應被回收的對象因爲其他對象的引用而不能被回收,從而在堆中寄存,造成內存泄露
- 內存溢出:無法爲對象分配足夠的內存
內存泄露是內存溢出的主要誘因。
原因:
- 內存泄露
- 長週期對象持有短週期對象的引用
- 對象地址被更改,從而找不到對象(常見於哈希值)
- 長時間進行耗費資源的連接
- 內存溢出
- 堆內存,對象申請過多
- 棧內存:遞歸太深或者方法調用層級過多
- 方法區:加載的類過多
避免:
- 不要在循環中創建對象
- 不要一次調用過多數據
- 大量字符串使用StringBuffer
- 方法區很少進行垃圾回收,儘量避免申請常量和靜態變量
5.給對象分配內存如何保證線程安全?
- CAS+失敗重試:CAS是樂觀鎖,每次都假設成功,在執行,失敗就重試,找到成功。保證更新的原子性
- TLAB:爲每一個線程,預先在Eden區域分配一塊內存,對象先分配到這裏,當該區域內存不足或用完之後,使用CAS+失敗重試機制
【Java 面試那點事】
這裏致力於分享 Java 面試路上的各種知識,無論是技術還是經驗,你需要的這裏都有!
這裏可以讓你【快速瞭解 Java 相關知識】,並且【短時間在面試方面有跨越式提升】
面試路上,你不孤單!