淺聊JVM--基礎版 一、來源 jvm共有三種 Sun公司: HotSpot使用最多 BEA:JRockit IBM:J9VM 今天我們主要瞭解的是Sun公司的HotSpot(關於HotSpot的愛恨情仇這裡就不做過多解釋了。)我們以前測試jdk是否安裝成功,java的環境變數是否配置成功會使用 ...
淺聊JVM--基礎版
一、來源
jvm共有三種
- Sun公司: HotSpot使用最多
- BEA:JRockit
- IBM:J9VM
今天我們主要瞭解的是Sun公司的HotSpot(關於HotSpot的愛恨情仇這裡就不做過多解釋了。)我們以前測試jdk是否安裝成功,java的環境變數是否配置成功會使用java -version命令來檢查。有一個細節大家可以看一下,cmd輸入java -version回車後,可以查看jvm。上圖
大家可以看到,我們目前使用大都是Sun公司的產品。那麼jvm處於什麼位置呢?其實jvm是在操作系統之上,和硬體並無直接聯繫。
面試題:jvm、jdk、jre的區別?
二、概述
JVM是Java Virtual Machine的縮寫,通俗來說也就是運行Java代碼的容器。當項目啟動時,會根據jvm相關配置參數,在電腦的記憶體中開啟一片空間用於運行jvm,之後Java相關代碼就會被載入進jvm中運行。那麼為什麼Java代碼可以實現“一次編譯,到處運行”的機制應該就與jvm有關了吧。
Java作為一種高級語言,要讓電腦執行Java程式,也得需要經過編碼-->編譯-->運行步驟。但是Java編譯程式並不能將Java源代碼直接編譯為電腦能識別的0/1指令,而是編譯為位元組碼文件(.class)這時候就需要jvm將位元組碼文件翻譯為與平臺有關的0/1指令。所以有了jvm,Java程式就達到了“一次編譯到處運行”的目的。所以其跨平臺好的根本原因就是因為jvm的存在。但是JVM並不代表就可以執行class了,JVM執行.class還需要JRE下的lib類庫的支持,尤其是rt.jar。
它的記憶體空間包括方法區、堆、方法棧、本地方法棧、PC寄存器。(以上5塊又成為運行時數據區)。我們剛纔也提到,Java源代碼經過Java編譯程式。能夠產生相應的.class文件,也就是位元組碼文件。位元組碼文件經過jvm中的解釋器,再次編譯成特定機器上的機器碼指令。上圖:
關於類載入器是如何工作的,大家可以參考文章:
[Java 類載入和類載入器 - 掘金 (juejin.cn)](https://juejin.cn/post/7226665757882236986)
面試題:什麼是雙親委派原則?
面試題:JNI(Java Native Interface)Java本地方法介面中Native關鍵字的作用?
我們今天主要淺聊一下PC寄存器、方法區、棧、堆
三、PC寄存器
程式計數器(Program Count Register):每個線程都有一個程式計數器,它是線程私有,是用來存儲指向下一條指令的地址,也就是即將來執行的指令代碼。在執行引擎讀取下一條指令,是一個非常小的記憶體空間。(關於程式計數器更多的細節或者是在迴圈、判斷等條件下PC是如何跳轉的,大家可以參考《電腦組成原理》類資料瞭解更多底層原理。)
四、方法區
方法區(Method Area):只是 JVM 規範中定義的一個概念,用於存儲被 JVM 載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據。具體放在哪裡,不同的實現可以放在不同的地方。HotSpot VM把 GC 分代收集擴展至方法區,即使用 Java 堆的永久代來實現方法區。(關於什麼是永久代,請向下看)
面試題:一張白紙,畫出對象實例化過程的記憶體圖?
五、棧
棧(Stack):裡面放的是8大基本數據類型+對象引用+實例的方法。棧記憶體里,主管程式的運行,生命周期和線程同步,線程結束,棧記憶體也就釋放了,棧記憶體結束,程式也就結束了。
我們都知道在Java中,程式員不需要顯示的去釋放一個對象的記憶體的,而是由虛擬機自行回收垃圾,那麼垃圾回收的過程不會涉及到棧(也就是說棧不存在垃圾回收的情況)
棧基於後進先出的特性,當執行完成後,會被彈出棧。
為什麼main()是先執行,最後結束?(因為一開始是main()方法最先入棧,最後彈出,結束執行)
棧溢出的問題(StackOverflow)
看下一面一段程式:
jvm中的棧為:
六、堆
堆:(Heap)運行時數據區,是被線程共用的一塊記憶體區域,創建的對象和數組都保存在 Java 堆記憶體中,也是垃圾收集器進行 垃圾收集的最重要的記憶體區域。堆記憶體細分為3個區域:
- 新生區(伊甸園去)--->Young/New(Eden Space)
- 養老區(Old)
- 永久區(Perm)
新生區又可以分為伊甸園區(Eden Space)、幸存0區、幸存1區,(幸存0區又可以稱為SurvivorFrom區、幸存1區可以稱為SurvivorTo區。From區和To區是可以來回動態交換的,具體什麼原因,大家可以查閱資料)
伊甸園區:所有的對象都是在伊甸園區new出來的。
幸存者(0、1)區:在伊甸園區里經過一次輕GC後活下來的進入幸存(0、1)區,被清理掉的就死亡掉。(關於GC是如何進行垃圾回收的,大家可以自行查閱資料,這裡只是簡單瞭解)
養老區:在經過多次的輕GC處理後,幸存(0,1)區也都滿了,會觸發重GC來進行清理伊甸園區和幸存(0,1)區,那麼進入養老區的就是重GC也未殺死的。若養老區也滿了,就相當於記憶體已滿,報OOM錯誤(java.lang.OutOfMemoryError:Java heap sapce)
聽聞:99%的對象都是臨時對象,能進入養老區的不多,所以OOM的錯誤也很少見。
面試題:工作中,遇到OOM了,你是怎麼排查的?
1、嘗試擴大堆記憶體空間,如果還滿,可能有垃圾代碼存在
2、分析記憶體,使用專業工具看哪裡出了問題
[面試官:工作中遇到過 OOM 嗎?你是怎麼排查的? - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/165981061)
擴大記憶體空間參數:
(具體的參數設置大家自己查閱)
例如:
永久區:這個區域常駐記憶體,用來存放JDK自身攜帶的Class對象,Interface元數據,存儲的是java運行時的一些環境或類信息,該區域不存在垃圾回收GC。關閉虛擬機就會釋放這個記憶體。
關於永久區的更新換代:
jdk1.6之前:永久代,常量池在方法區
jdk1.7:永久代,但是慢慢退化了(去永久代)常量池在堆中
jdk1.8之後:無永久代,常量池在元空間
方法區又稱非堆,本質還是堆,只是為了區分概念。
OK,結束!!!
資料參考: