1、類加載子系統作用
- Class文件,class文件在文件開頭有特定的文件標識。
- 只負責class文件的加載,至於它是否可以運行,則由ExecutionEngine決定
- (這部分常量信息是Class文件中常量池部分的內存映射)
2、類加載器ClassLoader角色
3、類加載過程
加載:
1.通過一個類的全限定名獲取定義此類的二進制字節流
2.將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
3.在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口
補充:加載.class文件的方式
- ,典型場景: Web Applet
- zip壓縮包中讀取,成爲日後jar, war格式的基礎
- ,使用最多的是:動態代理技術
- ,典型場景: JSP應用
- .class文件,比較少見
- ,典型的防Class文件被反編譯的保護措施
驗證(Verify) :
- Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全
- ,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
準備(Prepare) :
- ,即零值。
- final修飾的static,因爲final在編譯的時候就會分配了,準備階段會顯式初始化;
- ,類變量會分配在方法區中,而實例變量是會隨着對象一起分配到Java堆中
解析(Resolve) :
- ,解析操作往往會伴隨着JVM在執行完初始化之後再執行。
- java虛擬機規範》的Class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的
CONSTANT_Class_info, CONSTANT_Fieldref_info, CONSTANT_Methodref_info等
初始化:
- <clinit> ()的過程。
- ,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合併而來。
- 不同於類的構造器。(關聯:構造器是虛擬機視角下的<init> ())
- , JVM會保證子類的<clinit> ()執行前,父類的<clinit>()已經執行完畢。
- <clinit> ()方法在多線程下被同步加鎖。
4、類加載器分類
- 支持兩種類型的類加載器,分別爲引導類加載器(Bootstrap
ClassLoader)和自定義類加載器(User-Defined ClassLoader)
- , 自定義類加載器一般指的是程序中由開發人員自定義的一類類加載器,但是Java虛擬機規範卻沒有這麼定義,而是將所有派生於抽象類ClassLoader的類加載器都劃分爲自定義類加載器。
- ,在程序中我們最常見的類加載器始終只有3個,如下所示:
5、虛擬機自帶的加載器
(引導類加載器, Bootstrap ClassLoader)
①這個類加載使用c/C++語言實現的,嵌套在JVM內部。
②它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.cIass .path路徑下的內容) ,用於提供JVM自身需要的類
③並不繼承自java.lang.ClassLoader,沒有父加載器。
④加載擴展類和應用程序類加載器,並指定爲他們的父類加載器。
⑤出於安全考慮, Bootstrap啓動類加載器只加載包名爲java, javax.sun等開頭的類
(Extension ClassLoader)
①Java語言編寫,由sun.misc. Launcher$ExtClassLoader實現。
②派生於ClassLoader類
③父類加載器爲啓動類加載器
④從java. ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JAR放在此目錄下,也會自動由擴展類加載器加載。
(系統類加載器, AppClassLoader)
① java語言編寫,由sun.misc. Launcher$AppClassLoader實現
②派生於ClassLoader類
③父類加載器爲擴展類加載器
④它負責加載環境變量classpath或系統屬性java.class.path指定路徑下的類庫
⑤該類加載是程序中默認的類加載器,一般來說, Java應用的類都是由它來完成加載
⑥通過ClassLoader#getSystemClassLoader ()方法可以獲取到該類加載器
在Java的日常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,在必要時,我們還可以自定義類加載器,來定製類的加載方式。
爲什麼要自定義類加載器?
1.開發人員可以通過繼承抽象類java.lang.ClassLoader類的方式,實現自己的類加載器,以滿足一些特殊的需求
2.在JDK1.2之前,在自定義類加載器時,總會去繼承ClassLoader類並重寫loadclass ()方法,從而實現自定義的類加載類,但是在JDK1.2之後已不再建議用戶去覆蓋loadclass ()方法,而是建議把自定義的類加載邏輯寫在findClass ()方法中
3.在編寫自定義類加載器時,如果沒有太過於複雜的需求,可以直按繼承URLCIassLoader類,這樣就可以避免自己去編寫findClass ()方法及其獲取字節碼流的方式,使自定義類加載器編寫更加簡潔。
6、關於ClassLoader
ClassLoader類,它是一個抽象類,其後所有的類加載器都繼承自ClassLoader (不包括啓動類加載器)
方法名稱 |
描述 |
getParent() |
返回該類加載器的超類加載器 |
loadClass(String name) |
加載名稱爲name的類,返回結果爲java.lang.Class類的實例 |
findClass(String name) |
查找名稱爲name的類,返回結果爲java.lang.Class類的實例 |
findLoadedClass(String name) |
查找名稱爲name的已經被加載過的類,返回結果爲java.lang.Class類的實例 |
defineClass(String name,byte[] b,int off,int len) |
把字節數組b中的內容轉換爲一個Java類,返回結果爲java.lang.Class類的實例 |
resolveClass(Class<?>c) |
連接指定的一個Java類 |
7、獲取ClassLoader的途徑
方式一:獲取當前類的ClassLoader() |
clazz.getClassLoader () |
方式二:獲取當前線程上下文的ClassLoader |
Thread. currentThread().getContextClassLoader() |
方式三:獲取系統的ClassLoader |
ClassLoader. getSystemClassLoader() |
方式四:獲取調用者的ClassLoader |
DriverManager.getCallerClassLoader () |
8、雙親委派機制
Java虛擬機對class文件採用的是按需加載的方式,也就是說當需要使用該類時纔會將它的class文件加載到內存生成class對象。而且加載某個類的class文件時, Java虛擬機採用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式。
工作原理:
1)如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委託給父類的加載器去執行;
2)如果父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啓動類加載器;
3)如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器纔會嘗試自己去加載,這就是雙親委派模式。
雙親委派機制舉例2
優勢
防止核心API被隨意篡改
java.lang.String
java. lang.Shkstart
java.lang.SecurityException: Prohibited package name: java.lang
沙箱安全機制
自定義String類,但是在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程中會先加載jdk自帶的文件(rt.jar包中java\lang\string.class),報錯信息說沒有main方法,就是因爲加載的是rt.jar包中的string類。這樣可以保證對java核心源代碼的保護,這就是沙箱安全機制。
9、其他
- JVM中表示兩個class對象是否爲同一個類存在兩個必要條件:
- ,包括包名。
- ClassLoader (指ClassLoader實例對象)必須相同。
- ,在JVM中,即使這兩個類對象(class對象)來源同一個Class文件,被同一個虛擬機所加載,但只要加載它們的ClassLoader實例對象不同,那麼這兩個類對象也是不相等的。
對類加載器的引用
JVM必須知道一個類型是由啓動加載器加載的還是由用戶類加載器加載的。如果一個類型是由用戶類加載器加載的,那麼JVM會將這個類加載器的一個引用作爲類型信息的一部分保存在方法區中。當解析一個類型到另一個類型的引用的時候, JVM需要保證這兩個類型的類加載器是相同的。
類的主動使用和被動使用
Java程序對類的使用方式分爲:主動使用和被動使用。
- ,又分爲七種情況:
- ,或者對該靜態變量賦值
- (比如: Class.forName ("com.atguigu.Test") )
- 虛擬機啓動時被標明爲啓動類的類
- 7開始提供的動態語言支持:
java. lang. invoke. MethodHandle實例的解析結果
REF_getStatic, REF_putStatic, REF_invokeStatic句柄對應的類沒有初始化,則初始化
其他使用Java類的方式都被看作是對類的被動使用,都不會導致類的初始化。
三、運行時數據區概述及線程
內存是非常重要的系統資源,是硬盤和CPU的中間倉庫及橋樑,承載着操作系統和應用程序的實時運行。JVM內存佈局規定了Java在運行過程中內存申請、分配、管理的策略,保證了JVM的高效穩定運行。不同的JVM對於內存的劃分方式和管理機制存在着部分差異。結合JVM虛擬機規範,來探討一下經典的JVM內存佈局。
Java虛擬機定義了若干種程序運行期間會使用到的運行時數據區,其中有一些會隨着虛擬機啓動而創建,隨着虛擬機退出而銷燬。另外一些則是與線程一一對應的,這些與線程對應的數據區域會隨着線程開始和結束而創建和銷燬。
灰色的爲單獨線程私有的,紅色的爲多個線程共享的。即:
獨立包括程序計數器、棧、本地棧。
(永久代或元空間、代碼緩存)
1、關於線程間共享的說明
每個JVM只有一個Runtime實例。即爲運行時環境,相當於內存結構的中間的那個框框:運行時環境。
2、線程
- JVM允許一個應用有多個線程並行的執行。
- Hotspot JVM裏,每個線程都與操作系統的本地線程直接映射。
- Java線程準備好執行以後,此時一個操作系統的本地線程也同時創建。Java線程執行終止後,本地線程也會回收。
- CPU上。一旦本地線程初始化成功,它就會調用Java線程中的run ()方法。
守護線程、普通線程
3、JVM系統線程
- console或者是任何一個調試工具,都能看到在後臺有許多線程在運行。這些後臺線程不包括調用public static void main(string[])的main線程以及所有這個main線程自己創建的線程
- Hotspot JVM裏主要是以下幾個:
- :這種線程的操作是需要JVM達到安全點纔會出現。這些操作必須在不同的線程中發生的原因是他們都需要JVM達到安全點,這樣堆纔不會變化。這種線程的執行類型包括"stop-the-world"的垃圾收集,線程棧收集,線程掛起以及偏向鎖撤銷。
- :這種線程是時間週期事件的體現(比如中斷),他們一般用於週期性操作的調度執行。
- 線程:這種線程對在JVM裏不同種類的垃圾收集行爲提供了支持。
- :這種線程在運行時會將字節碼編譯成到本地代碼。
- :這種線程接收信號併發送給JVM,在它內部通過調用適當的方法進行處理。
四、程序計數器(PC 寄存器)
1、PC Register介紹
JVM中的程序計數寄存器(Program Counter Register)中, Register的命名源於CPU的寄存器,寄存器存儲指令相關的現場信息。 CPU只有把數據裝載到寄存器才能夠運行。
這裏,並非是廣義上所指的物理寄存器,或許將其翻譯爲PC計數器(或指令計數器)會更加貼切(也稱爲程序鉤子) ,並且也不容易引起一些不必要的誤會。JVM中的PC寄存器是對物理PC寄存器的一種抽象模擬。
作用:
PC寄存器用來存儲指向下一條指令的地址,也即將要執行的指令代碼。由執行引擎讀取下一條指令。
- ,幾乎可以忽略不記。也是運行速度最快的存儲區域。
- JVM規範中,每個線程都有它自己的程序計數器,是線程私有的,生命週期與線程的生命週期保持一致。
- ,也就是所謂的當前方法。程序計數器會存儲當前線程正在執行的Java方法的JVM指令地址;或者,如果是在執行native方法,則是未指定值(undefined)
- ,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。
- Java虛擬機規範中沒有規定任何OutofMemoryError情況的區域。
2、舉例說明
3、兩個常見問題
- PC寄存器存儲字節碼指令地址有什麼用呢?
爲什麼使用PC寄存器記錄當前線程的執行地址呢?
因爲CPU需要不停的切換各個線程,這時候切換回來以後,就得知道接着從哪開始繼續執行。
JVM的字節碼解釋器就需要通過改變PC寄存器的值來明確下一條應該執行什麼樣的字節碼指令。
- 寄存器爲什麼會被設定爲線程私有?
我們都知道所謂的多線程在一個特定的時間段內只會執行其中某一個程的方法, CPU會不停地做任務切換,這樣必然導致經常中斷或恢復,如何保證分毫無差呢?爲了能夠準確地記錄各個線程正在執行的當前字節碼指令地址,最好的辦法自然是爲每一個線程都分配一個PC寄存器,這樣一來各個線程之間便可以進行獨立計算,從而不會出現相互干擾的情況。
由於CPU時間片輪限制,衆多線程在併發執行過程中,任何一個確定的時刻,一個處理器或者多核處理器中的一個內核,只會執行某個線程中的一條指令。
這樣必然導致經常中斷或恢復,如何保證分毫無差呢?每個線程在創建後,都會產生自己的程序計數器和棧幀,程序計數器在各個線程之間互不影響。
CPU時間片
CPU時間片即CPU分配給各個程序的時間,每個線程被分配一個時間段,稱作它的時間片。
在宏觀上:我們可以同時打開多個應用程序,每個程序並行不悖,同時運行。
但在微觀上:由於只有一個CPU,一次只能處理程序要求的一部分,如何處理公平,一種方法就是引入時間片,每個程序輪流執行。