JVM(Hotspot)
JVM(Hotspot)
一.介紹
- JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。
- 引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。
##二.JVM結構圖
2.1程序計數器(PC寄存器)
? 程序計數器是一塊很小的空間,它可以看作是當前線程所執行的字節碼的行號指示器。因為代碼是在線程中運行的,線程有可能被掛起,即CPU一會兒執行線程A,但是線程A還沒有執行完被掛起了,接著它又去執行B,最后又來執行線程A,CPU必須得知道執行線程A的哪一部分指令,而線程計數器會告訴CPU。
2.2 虛擬機棧(java棧)
? 虛擬機棧存儲當前線程運行方法所需要的數據,指令,返回地址。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息。每個方法從調用直到執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
2.3 本地方法棧
? 本地方法棧與虛擬機棧鎖發揮的作用是非長相似的,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。
2.4 Java堆
? Java堆是java虛擬機鎖管理的內存中最大的一塊,所謂的JVM調優,絕大多數情況下是在這里面調它的參數。Java堆是所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。
2.5 方法區
? 方法區與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯后的代碼等數據。
三.雙親委派
3.1ClassLoader
JVM中提供了三層的ClassLoader:
- Bootstrap classLoader:主要負責加載核心的類庫(java.lang.*等),構造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要負責加載jre/lib/ext目錄下的一些擴展的jar。
- AppClassLoader:主要負責加載應用程序的主函數類
3.2 加載流程
(此處的父類子類并不是真正的父類子類,只是名義上的)
? 從上圖中我們就容易理解了, 當Hello.class文件要被加載時,首先會在AppClassLoader中檢查是否加載過,如果有那就無需加載了,如果沒有的話,那么就會拿到父類的加載器(ExtClassLoader),然后調用父類加載器的loadClass方法。同理,父類中也會先檢查自己是否加載過,如果沒有就往上走,這就類似于一個遞歸的過程,直到到達Bootstrap classLoader之前,都是在檢查自己是否加載過,并不會去自己加載。直到Bootstrap classLoader,已經沒有父類加載器了,這時候就考慮自己是否能加載,如果自己可以加載,那么就直接自己加載此類,如果不能,則會下沉到子加載器中去加載,子加載器同樣會判斷自己是否能加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException。
3.3 為什么要設置這種機制
? 這種設計有個好處是,如果有人想替換系統級別的類,如String.java,想篡改它的實現,在這種機制下這些系統的類已經被Bootstrap classLoader加載過了。為什么? 因為當一個類需要加載的時候,首先去嘗試加載的就是Bootstrap classLoader,所以其它類加載器沒有機會再去加載,因此從一定程度上防止了惡意代碼的植入。
3.4 這種機制的弊端
? 雙親委派很好地解決了各個類加載器的基礎類統一問題(越基礎的類由越上層的類加載器進行加載),基礎類之所以稱為"基礎",是因為它們總是作為被用戶代碼調用的API,但世事往往沒有絕對的完美,如果基礎類又要調回用戶的代碼,那該怎么辦?
3.5 如何打破這種機制
典型的兩個方法:
- 自定義類加載器,重寫loadClass()方法和findClass()方法 (繼承ClassLoader)
- 使用線程上下文類加載器
方式一:自定義類加載器
? 重寫loadClass()就是重新指定全限定類名加載class,重寫了這個方法以后就能自定義使用什么加載器了,也可以自定義加載委派機制,也就打破了雙親委派的模型。
方式二:線程上下文類加載器(Thread Context ClassLoader)
? 這類加載器可以通過java.lang.Thread類的setContextClassLoader方法進行設置,如果不特殊設置就會從父類繼承,一般默認使用的是應用程序類加載器(也就是系統加載器),很明顯,線程上下文類加載器讓父級類加載器能夠通過子級類加載器來加載類,這就打破了雙親委派模型的原則。
JDBC就比較典型
? 第一:獲取線程上下文類加載器,從而也就獲得了應用程序類加載器,也可能是自定義的類加載器。
? 第二:從META-INF/service/java.sql.Driver文件中獲取具體的實現類名 “com.mysql.jdbc.Driver”。
? 第三:通過線程上下文類加載器去加載這個Driver類,從而避開了雙親委派模型的弊端。
四.JDK、JRE、JVM的關系
五.內存模型(堆內存)
- 堆空間 = 新生代(1/3)+ 老年代(2/3)
- 新生代 = Eden(8/10) + From(1/8) + To(1/8)
- 元空間可以直接理解為物理內存
六.Java GC
6.1 介紹
? 如果不進行垃圾回收,內存遲早都會被消耗空,因為我們在不斷的分配內存空間而不進行回收。除非內存無限大,我們可以任性的分配而不回收,但是事實并非如此。所以,垃圾回收是必須的。 那么哪些內存需要回收,就是垃圾回收機制第一個要考慮的問題!
6.2 GC運行流程
? 大多數情況下,對象在Eden區分配,當Eden沒有足夠的內存空間時觸發一次Minor GC ,會將存活的對象從Eden區移動到S0內存區域,并清空Eden區, 當再次發生Minor GC時, 將Eden和S0中存活的對象移到S1區;存活對象反復在S0和S1移動(也就是From到To),當對象在Surivivor之間移動或者從Eden移動到Surivivor區時,對象的GC年齡會不斷增加,當GC年齡超過默認閾值15會進入老年代 ,老年代用于存放經過幾次MinorGC后依然存活的對象,當老年代空間不足時會觸發Full GC/Major GC,速度比MinorGC慢十多倍 。
6.3 如何判斷對象是否存活
-
引用計數法
? 在對象上添加一個引用計數器,每當一個地方引用這個對象時 ,計數器值+1;當引用失效時,計數器值-1,任何時刻計數值為0的對象就是不可能再被使用的。
優點:實現簡單,判定高效
缺點:很難解決對象之間相互引用的情況
-
可達性分析法
? 這個算法的基本思想是通過一系列稱為“GC Roots”的對象作為起始點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達)時,則證明此對象是不可用的。
6.4 垃圾回收算法
- Mark-Sweep(標記-清除)算法, 標記需要回收的對象,然后清除,會造成許多內存碎片。
- Copying(復制)算法 將內存分為兩塊,只使用一塊,進行垃圾回收時,先將存活的對象復制到另一塊區域,然后清空之前的區域。
- Mark-Compact(標記-整理)算法(壓縮法) 與標記清除算法類似,但是在標記之后,將存活對象向一端移動,然后清除邊界外的垃圾對象。
- Generational Collection(分代收集)算法 分為年輕代和老年代,年輕代時比較活躍的對象,使用復制算法做垃圾回收。老年代每次回收只回收少量對象,使用標記整理法。
6.5典型的垃圾回收器
- CMS
- G1
onal Collection(分代收集)算法 分為年輕代和老年代,年輕代時比較活躍的對象,使用復制算法做垃圾回收。老年代每次回收只回收少量對象,使用標記整理法。
6.5典型的垃圾回收器
- CMS
- G1
總結
以上是生活随笔為你收集整理的JVM(Hotspot)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类型多样的家居装饰C4D模型素材,速来收
- 下一篇: 每天一练——牛客网基础语法(9)