前言
如果在文中用詞或者理解方面出現問題,歡迎指出。此文旨在提及和而不深究,但會盡量效率地把知識點都抛出來
一、JVM的基本介紹
JVM 是 Java Virtual Machine 的縮寫,它是一個虛構出來的計算機,一種規範。通過在實際的計算機上仿真模擬各類計算機功能實現···
好,其實抛開這麽專業的句子不說,就知道JVM其實就類似于一台小電腦運行在windows或者linux這些操作系統環境下即可。它直接和操作系統進行交互,與硬件不直接交互,可操作系統可以幫我們完成和硬件進行交互的工作。
② 方法區
方法區 是用于存放類似于元數據信息方面的數據的,比如類信息,常量,靜態變量,編譯後代碼···等
類加載器將 .class 文件搬過來就是先丟到這一塊上
③ 堆
堆 主要放了一些存儲的數據,比如對象實例,數組···等,它和方法區都同屬于 線程共享區域 。也就是說它們都是 線程不安全 的
④ 棧
棧 這是我們的代碼運行空間。我們編寫的每一個方法都會放到 棧 裏面運行。
我們會聽說過 本地方法棧 或者 本地方法接口 這兩個名詞,不過我們基本不會涉及這兩塊的內容,它倆底層是使用C來進行工作的,和Java沒有太大的關系。
⑤ 程序計數器
主要就是完成一個加載工作,類似于一個指針一樣的,指向下一行我們需要執行的代碼。和棧一樣,都是 線程獨享 的,就是說每一個線程都會有自己對應的一塊區域而不會存在並發和多線程的問題。
一個main方法
3.3.8 如何判斷一個對象需要被幹掉
判斷一個對象的死亡至少需要兩次標記
- 如果對象進行可達性分析之後沒發現與GC Roots相連的引用鏈,那它將會第一次標記並且進行一次篩選。判斷的條件是決定這個對象是否有必要執行finalize()方法。如果對象有必要執行finalize()方法,則被放入F-Queue隊列中。
- GC對F-Queue隊列中的對象進行二次標記。如果對象在finalize()方法中重新與引用鏈上的任何一個對象建立了關聯,那麽二次標記時則會將它移出“即將回收”集合。如果此時對象還沒成功逃脫,那麽只能被回收了。
如果確定對象已經死亡,我們又該如何回收這些垃圾呢
3.4 垃圾回收算法
不會非常詳細的展開,常用的有標記清除,複制,標記整理和分代收集算法
3.4.1 標記清除算法
標記清除算法就是分爲“標記”和“清除”兩個階段。標記出所有需要回收的對象,標記結束後統一回收。這個套路很簡單,也存在不足,後續的算法都是根據這個基礎來加以改進的。
其實它就是把已死亡的對象標記爲空閑內存,然後記錄在一個空閑列表中,當我們需要new一個對象時,內存管理模塊會從空閑列表中尋找空閑的內存來分給新的對象。
不足的方面就是標記和清除的效率比較低下。且這種做法會讓內存中的碎片非常多。這個導致了如果我們需要使用到較大的內存塊時,無法分配到足夠的連續內存。比如下圖
不過它們分配的時候也不是按照1:1這樣進行分配的,就類似于Eden和Survivor也不是等價分配是一個道理。
3.4.3 標記整理算法
複制算法在對象存活率高的時候會有一定的效率問題,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存
到jdk8爲止,默認的垃圾收集器是Parallel Scavenge 和 Parallel Old
從jdk9開始,G1收集器成爲默認的垃圾收集器
目前來看,G1回收器停頓時間最短而且沒有明顯缺點,非常適合Web應用。在jdk8中測試Web應用,堆內存6G,新生代4.5G的情況下,Parallel Scavenge 回收新生代停頓長達1.5秒。G1回收器回收同樣大小的新生代只停頓0.2秒。
3.6 (了解)JVM的常用參數
設置一個VM options的參數
-Xmx20m -Xms5m -XX:+PrintGCDetails 複制代碼
這裏GC彈出了一個Allocation Failure分配失敗,這個事情發生在PSYoungGen,也就是年輕代中
這時候申請到的內存爲18M,空閑內存爲4.214195251464844M
我們此時創建一個字節數組看看,執行下面的代碼
byte[] b = new byte[1 * 1024 * 1024]; System.out.println(“分配了1M空間給數組”); System.out.println(“Xmx=” + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + “M”); //系統的最大空間 System.out.println(“free mem=” + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + “M”); //系統的空閑空間 System.out.println(“total mem=” + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + “M”); 複制代碼
這時候我們創建了一個10M的字節數據,這時候最小堆內存是頂不住的。我們會發現現在的total memory已經變成了15M,這就是已經申請了一次內存的結果。
此時我們再跑一下這個代碼
System.gc(); System.out.println(“Xmx=” + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + “M”); //系統的最大空間 System.out.println(“free mem=” + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + “M”); //系統的空閑空間 System.out.println(“total mem=” + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + “M”); //當前可用的總空間 複制代碼