【Java開發(fā)】Java面試七大專題第5篇:4. 內(nèi)存溢出,5. 類加載【附代碼文檔】

??????教程全知識點簡介:基礎(chǔ)篇 1. 二分查找 2. 冒泡排序 7. ArrayList 8. Iterator 9. LinkedList 10. HashMap 1)基本數(shù)據(jù)結(jié)構(gòu) 2)樹化與退化 3)索引計算 4)put 與擴(kuò)容 5)并發(fā)問題 11. 單例模式 并發(fā)篇 1. 線程狀態(tài) 3. wait vs sleep 4. lock vs synchronized 虛擬機(jī)篇 1. JVM 內(nèi)存結(jié)構(gòu) 4. 內(nèi)存溢出 5. 類加載 6. 四種引用 7. finalize 框架篇 1. Spring refresh 流程 2. Spring bean 生命周期 6. Spring 注解 7. SpringBoot 自動配置原理 數(shù)據(jù)庫篇 1. 隔離級別 2. 快照讀與當(dāng)前讀 3. InnoDB vs MyISAM 4. 索引 索引基礎(chǔ) 5. 查詢語句執(zhí)行流程 6. undo log 與 redo log 7. 鎖 緩存篇 1. Redis 數(shù)據(jù)類型 2. keys 命令問題 3. 過期 key 的刪除策略 5. 緩存問題 6. 緩存原子性 7. LRU Cache 實現(xiàn) 分布式篇 1. CAP 定理 2. Paxos 算法 4. Gossip 協(xié)議 5. 分布式通用設(shè)計 6. 一致性 Hash(補(bǔ)充)


??????????git倉庫code.zip 直接get:???https://gitlab.com/yiqing112/backend/-/blob/main/Java/Java面試七大專題/note.md ???????

? 本教程項目亮點

?? 知識體系完整:覆蓋從基礎(chǔ)原理、核心方法到高階應(yīng)用的全流程內(nèi)容
?? 全技術(shù)鏈覆蓋:完整前后端技術(shù)棧,涵蓋開發(fā)必備技能
?? 從零到實戰(zhàn):適合 0 基礎(chǔ)入門到提升,循序漸進(jìn)掌握核心能力
?? 豐富文檔與代碼示例:涵蓋多種場景,可運行、可復(fù)用
?? 工作與學(xué)習(xí)雙參考:不僅適合系統(tǒng)化學(xué)習(xí),更可作為日常開發(fā)中的查閱手冊
?? 模塊化知識結(jié)構(gòu):按知識點分章節(jié),便于快速定位和復(fù)習(xí)
?? 長期可用的技術(shù)積累:不止一次學(xué)習(xí),而是能伴隨工作與項目長期參考


??????全教程總章節(jié)


??????本篇主要內(nèi)容

4. 內(nèi)存溢出

要求

  • 能夠說出幾種典型的導(dǎo)致內(nèi)存溢出的情況

典型情況

  • 誤用線程池導(dǎo)致的內(nèi)存溢出
    • 參考 day03.TestOomThreadPool
  • 查詢數(shù)據(jù)量太大導(dǎo)致的內(nèi)存溢出
    • 參考 day03.TestOomTooManyObject
  • 動態(tài)生成類導(dǎo)致的內(nèi)存溢出
    • 參考 day03.TestOomTooManyClass

5. 類加載

要求

  • 掌握類加載階段
  • 掌握類加載器
  • 理解雙親委派機(jī)制

類加載過程的三個階段

  1. 加載

    1. 將類的字節(jié)碼載入方法區(qū),并創(chuàng)建類.class 對象

    2. 如果此類的父類沒有加載,先加載父類

    3. 加載是懶惰執(zhí)行

Mkyong Java 教程

  1. 鏈接

    1. 驗證 – 驗證類是否符合 Class 規(guī)范,合法性、安全性檢查
    2. 準(zhǔn)備 – 為 static 變量分配空間,設(shè)置默認(rèn)值
    3. 解析 – 將常量池的符號引用解析為直接引用
  2. 初始化

    1. 靜態(tài)代碼塊、static 修飾的變量賦值、static final 修飾的引用類型變量賦值,會被合并成一個 <cinit> 方法,在初始化時被調(diào)用
    2. static final 修飾的基本類型變量賦值,在鏈接階段就已完成
    3. 初始化是懶惰執(zhí)行

驗證手段

  • 使用 jps 查看進(jìn)程號
  • 使用 jhsdb 調(diào)試,執(zhí)行命令 jhsdb.exe hsdb 打開它的圖形界面
    • Class Browser 可以查看當(dāng)前 jvm 中加載了哪些類
    • 控制臺的 universe 命令查看堆內(nèi)存范圍
    • 控制臺的 g1regiondetails 命令查看 region 詳情
    • scanoops 起始地址 結(jié)束地址 對象類型 可以根據(jù)類型查找某個區(qū)間內(nèi)的對象地址
    • 控制臺的 inspect 地址 指令能夠查看這個地址對應(yīng)的對象詳情
  • 使用 javap 命令可以查看 class 字節(jié)碼

代碼說明

  • day03.loader.TestLazy - 驗證類的加載是懶惰的,用到時才觸發(fā)類加載
  • day03.loader.TestFinal - 驗證使用 final 修飾的變量不會觸發(fā)類加載

Spring Data JPA 文檔

jdk 8 的類加載器

名稱 加載哪的類 說明
Bootstrap ClassLoader JAVA_HOME/jre/lib 無法直接訪問
Extension ClassLoader JAVA_HOME/jre/lib/ext 上級為 Bootstrap,顯示為 null
Application ClassLoader classpath 上級為 Extension
自定義類加載器 自定義 上級為 Application

雙親委派機(jī)制

Feign 文檔

Vavr 函數(shù)式庫

所謂的雙親委派,就是指優(yōu)先委派上級類加載器進(jìn)行加載,如果上級類加載器

  • 能找到這個類,由上級加載,加載后該類也對下級加載器可見
  • 找不到這個類,則下級類加載器才有資格執(zhí)行加載

雙親委派的目的有兩點

  1. 讓上級類加載器中的類對下級共享(反之不行),即能讓你的類能依賴到 jdk 提供的核心類

  2. 讓類的加載有優(yōu)先次序,保證核心類優(yōu)先加載

對雙親委派的誤解

Javatpoint 教程

下面面試題的回答是錯誤的

錯在哪了?

  • 自己編寫類加載器就能加載一個假冒的 java.lang.System 嗎? 答案是不行。

  • 假設(shè)你自己的類加載器用雙親委派,那么優(yōu)先由啟動類加載器加載真正的 java.lang.System,自然不會加載假冒的

  • 假設(shè)你自己的類加載器不用雙親委派,那么你的類加載器加載假冒的 java.lang.System 時,它需要先加載父類 java.lang.Object,而你沒有用委派,找不到 java.lang.Object 所以加載會失敗

  • 以上也僅僅是假設(shè)。事實上操作你就會發(fā)現(xiàn),自定義類加載器加載以 java. 打頭的類時,會拋安全異常,在 jdk9 以上版本這些特殊包名都與模塊進(jìn)行了綁定,更連編譯都過不了

代碼說明

  • day03.loader.TestJdk9ClassLoader - 演示類加載器與模塊的綁定關(guān)系

6. 四種引用

要求

  • 掌握四種引用

強(qiáng)引用

  1. 普通變量賦值即為強(qiáng)引用,如 A a = new A();

  2. 通過 GC Root 的引用鏈,如果強(qiáng)引用不到該對象,該對象才能被回收

OkHttp 文檔

<img src="https://upload-images.jianshu.io/upload_images/29644671-5b3500f0ca26f4b2.png" alt="image-20210901111903574" style="zoom:80%;" />

軟引用(SoftReference)

  1. 例如:SoftReference a = new SoftReference(new A());

  2. 如果僅有軟引用該對象時,首次垃圾回收不會回收該對象,如果內(nèi)存仍不足,再次回收時才會釋放對象

  3. 軟引用自身需要配合引用隊列來釋放

  4. 典型例子是反射數(shù)據(jù)

<img src="https://upload-images.jianshu.io/upload_images/29644671-a288ad331b931903.png" alt="image-20210901111957328" style="zoom:80%;" />

弱引用(WeakReference)

  1. 例如:WeakReference a = new WeakReference(new A());

  2. 如果僅有弱引用引用該對象時,只要發(fā)生垃圾回收,就會釋放該對象

SpringDoc OpenAPI

  1. 弱引用自身需要配合引用隊列來釋放

  2. 典型例子是 ThreadLocalMap 中的 Entry 對象

<img src="https://upload-images.jianshu.io/upload_images/29644671-5c5144971e839bb8.png" alt="image-20210901112107707" style="zoom:80%;" />

虛引用(PhantomReference)

  1. 例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);

  2. 必須配合引用隊列一起使用,當(dāng)虛引用所引用的對象被回收時,由 Reference Handler 線程將虛引用對象入隊,這樣就可以知道哪些對象被回收,從而對它們關(guān)聯(lián)的資源做進(jìn)一步處理

  3. 典型例子是 Cleaner 釋放 DirectByteBuffer 關(guān)聯(lián)的直接內(nèi)存

<img src="https://upload-images.jianshu.io/upload_images/29644671-e47aec54850bd437.png" alt="image-20210901112157901" style="zoom:80%;" />

代碼說明

  • day03.reference.TestPhantomReference - 演示虛引用的基本用法
  • day03.reference.TestWeakReference - 模擬 ThreadLocalMap, 采用引用隊列釋放 entry 內(nèi)存

7. finalize

要求

  • 掌握 finalize 的工作原理與缺點

finalize

SpotBugs 文檔

  • 它是 Object 中的一個方法,如果子類重寫它,垃圾回收時此方法會被調(diào)用,可以在其中進(jìn)行資源釋放和清理工作
  • 將資源釋放和清理放在 finalize 方法中非常不好,非常影響性能,嚴(yán)重時甚至?xí)?OOM,從 Java9 開始就被標(biāo)注為 @Deprecated,不建議被使用了

finalize 原理

  1. 對 finalize 方法進(jìn)行處理的核心邏輯位于 java.lang.ref.Finalizer 類中,它包含了名為 unfinalized 的靜態(tài)變量(雙向鏈表結(jié)構(gòu)),F(xiàn)inalizer 也可被視為另一種引用對象(地位與軟、弱、虛相當(dāng),只是不對外,無法直接使用)
  2. 當(dāng)重寫了 finalize 方法的對象,在構(gòu)造方法調(diào)用之時,JVM 都會將其包裝成一個 Finalizer 對象,并加入 unfinalized 鏈表中

Apache DBCP 文檔

  1. Finalizer 類中還有另一個重要的靜態(tài)變量,即 ReferenceQueue 引用隊列,剛開始它是空的。當(dāng)狗對象可以被當(dāng)作垃圾回收時,就會把這些狗對象對應(yīng)的 Finalizer 對象加入此引用隊列
  2. 但此時 Dog 對象還沒法被立刻回收,因為 unfinalized -> Finalizer 這一引用鏈還在引用它嘛,為的是【先別著急回收啊,等我調(diào)完 finalize 方法,再回收】
  3. FinalizerThread 線程會從 ReferenceQueue 中逐一取出每個 Finalizer 對象,把它們從鏈表斷開并真正調(diào)用 finallize 方法
  1. 由于整個 Finalizer 對象已經(jīng)從 unfinalized 鏈表中斷開,這樣沒誰能引用到它和狗對象,所以下次 gc 時就被回收了

finalize 缺點

  • 無法保證資源釋放:FinalizerThread 是守護(hù)線程,代碼很有可能沒來得及執(zhí)行完,線程就結(jié)束了
  • 無法判斷是否發(fā)生錯誤:執(zhí)行 finalize 方法時,會吞掉任意異常(Throwable)
  • 內(nèi)存釋放不及時:重寫了 finalize 方法的對象在第一次被 gc 時,并不能及時釋放它占用的內(nèi)存,因為要等著 FinalizerThread 調(diào)用完 finalize,把它從 unfinalized 隊列移除后,第二次 gc 時才能真正釋放內(nèi)存
  • 有的文章提到【Finalizer 線程會和 的主線程進(jìn)行競爭,不過由于它的優(yōu)先級較低,獲取到的CPU時間較少,因此它永遠(yuǎn)也趕不上主線程的步伐】這個顯然是錯誤的,F(xiàn)inalizerThread 的優(yōu)先級較普通線程更高,原因應(yīng)該是 finalize 串行執(zhí)行慢等原因綜合導(dǎo)致

代碼說明

  • day03.reference.TestFinalize - finalize 的測試代碼

JConsole 文檔


??? (未完待續(xù))項目系列下一章

??下一篇 將進(jìn)入更精彩的環(huán)節(jié)!
?? 記得收藏 & 關(guān)注,第一時間獲取更新!
?? 一起見證整個系列逐步成型的全過程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容