初步了解java虚拟机
1. jdk, jre, jvm 關系
基本介紹:
JDK(Java Development Kit)是針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。
Java Runtime Environment(JRE)是運行JAVA程序所必須的環境的集合,包含JVM標準實現及Java核心類庫。
JVM是Java Virtual Machine(Java虛擬機)的縮寫,是整個java實現跨平臺的最核心的部分,能夠運行以Java語言寫作的軟件程序。
1、JDK
JDK是java開發工具包,在其安裝目錄下面有六個文件夾、一些描述文件、一個src壓縮文件。bin、include、lib、 jre這四個文件夾起作用,demo、sample是一些例子。可以看出來JDK包含JRE,而JRE包含JVM。
bin:最主要的是編譯器(javac.exe)*
include:java和JVM交互用的頭文件
lib:類庫
jre:java運行環境(注意:這里的bin、lib文件夾和jre里的bin、lib是不同的)
總的來說JDK是用于java程序的開發,而jre則是只能運行class而沒有編譯的功能。
JDK是提供給Java開發人員使用的,其中包含了java的開發工具,也包括了JRE。所以安裝了JDK,就不用在單獨安裝JRE了。 其中的開發工具包括編譯工具(javac.exe)打包工具(jar.exe)等
2、JRE
JRE是指java運行環境。光有JVM還不能成class的執行,因為在解釋class的時候JVM需要調用解釋所需要的類庫lib。在JDK的安裝目錄里你可以找到jre目錄,里面有兩個文件夾bin和lib,在這里可以認為bin里的就是jvm,lib中則是jvm工作所需要的類庫,而jvm和 lib和起來就稱為jre。所以,在你寫完java程序編譯成.class之后,你可以把這個.class文件和jre一起打包發給朋友,這樣你的朋友就可以運行你寫程序了。
包括Java虛擬機(JVM Java Virtual Machine)和Java程序所需的核心類庫等,
如果想要運行一個開發好的Java程序,計算機中只需要安裝JRE即可。
3、JVM
JVM就是我們常說的java虛擬機,它是整個java實現跨平臺的最核心的部分,所有的java程序會首先被編譯為.class的類文件,這種類文件可以在虛擬機上執行,也就是說class并不直接與機器的操作系統相對應,而是經過虛擬機間接與操作系統交互,由虛擬機將程序解釋給本地系統執行。可以理解為是一個虛擬出來的計算機,具備著計算機的基本運算方式,它主要負責將java程序生成的字節碼文件解釋成具體系統平臺上的機器指令。讓具體平臺如window運行這些Java程序。
JVM:將字節碼文件轉成具體系統平臺的機器指令。
JRE:JVM+Java語言的核心類庫。
JDK:JRE+Java的開發工具。
二、了解以下未來jdk的新技術發展
Java 11 將是未來 Java 用戶的最可能選項;
如果一個公司對大堆棧 GC 能力、延遲 SLA 等方面要求沒有那么高,就沒有足夠動力去做相關升級,也未必有技術力量解決版本評估、兼容性修正等現實問題;
Java 新版本升級在中國的宣傳還是不夠,如果很多企業看不到技術升級的紅利,勢必也影響升級的積極性。
雖然國內很多頭部廠商都在定制 OpenJDK,但是目前定制 OpenJDK 被采用范圍還都有限,主體使用還是 Oracle JDK(根據《JVM 生態系統報告 2018》調查顯示,70% 的開發者選擇使用 Oracle JDK,21% 的開發者選擇使用 OpenJDK);
廠商是否轉向 OpenJDK,還有一個重要考量因素就是看他們是否愿意付費使用 OracleJDK,如果不是的話,未來 OpenJDK 可能會逐漸取代 Oracle JDK,目前國內頭部廠商都在 OpenJDK 上有所動作; (對于參與 OpenJDK 的國內頭部廠商來說,可能他們的看法更加積極,他們把 OpenJDK 定義在早期大眾階段)
大家在公有云、私有云等方面的競爭格局,深刻影響著在 OpenJDK 上的競爭格局;
OpenJDK 很可能被認為是一種退?求其次的選擇。
Graal VM 目前還尚不可知其兼容性情況以及明確的商業化條款;
Graal VM 的部分技術,例如,基于 Java 語言開發的 JIT 引擎,可能會成為未來 OpenJDK 的基礎技術;
在國內,懷疑 Graal VM、IBM OpenJ9 進入普遍生產實踐的可能性會比較低。
Lambda 語法以及 Stream API 也在開發人員的?常?作中?泛地運用,并且沒有看到語法回退的趨勢;
Vector API 等前沿特性,有能力的公司有限,抑制了對其有需求的公司或者場景。
Groovy 已快成為明日黃花,往昔的光芒逐漸地被后起之秀 Kotlin 替代;
Scala 在適合的領域做王者就夠了,主流不主流沒那么重要;
Kotlin 被谷歌強推,谷歌支持的基本上都成功了,但是對 Kotlin 未來發展空間還是表示懷疑;
網上很多文章都在鼓吹,說 Kotlin 最終會取代 Java 成為新一代 JVM 主流語言, 但是從誕生到現在,好像依然沒有語言能取代 Java。
微服務技術處于早期大眾與晚期大眾之間,新的微服務開發框架需要技術突破和創新,不然已經難有一席之地;
Java 不再是微服務唯一的選擇;
在技術多元化的今天,支持多語言的微服務開發框架是個必須品。
技術采用生命周期解讀
在上一章節我們已經先把各位專家的觀點和結論拋了出來,但是結論背后還需要很關鍵的原因解讀,所以這一章節就按照 Java/JVM、不同層次的主流框架、微服務這三個部分,來逐一呈現。
Java/JVM
其實在 Java 版本方面,各位專家的觀點完全一致:Java 13 處于創新者階段,Java 11 處于早期采用者階段,Java 8 處于晚期大眾階段。
在 InfoQ 面向開發者的 Java 使用版本調查中,毫無懸念,在參與問卷調研的開發者中,88.7% 正在使用 Java8 版本,這些人當中只有 35% 有升級計劃,剩余 65% 并沒有升級計劃。
楊曉峰認為這一情況也正常:Java8 在可預見的將來依然會是生產的主體,放在晚期大眾階段是合理的。但是對于很多頭部廠商來說,Java11 或者再后續版本,有可能陸續出現一定規模的生產化部署。他認為這樣的趨勢只會在頭部公司發生,如果一個公司對大堆棧 GC 能力、延遲 SLA 等方面要求沒有那么高,就沒有足夠動力去做相關升級,也未必有技術力量解決版本評估、兼容性修正等現實問題。所以結論就是:Java11 處于早期采用者階段。
對此黃飛補充:也正是因為 Java11 處于早期采用者階段,因此相關的資料較少,遇到問題會有比較高的學習成本,例如 JFR 對 11 的支持,JMC 對 Java11 的分析能力較弱。
而對于 Java 13,小馬哥認為該版本在新 GC 算法的提升以及 Socket 實現上的變化還是非常令?期待的,因此 Java 13 排在創新者之列。
對于 Java 的升級,Oracle 宣布從 Java 9 開始每半年將更新一個 Java 大版本——Java 11 是長期支持(Long-Term -Support, LTS)版本,Java 9、10 則成了過渡版本(non?LTS),因此,陳楚暉不建議用戶在生產中使用 Java 9、10。在他看來,小版本升級相對風險是比較小的,而大版本變更則會有可能需要更改大量的代碼,這也是為什么這么多人還在堅持用 Java8,而不去更新 Java 11、12、或者 13 的原因。
對于開發者升級 Java 動力不足的原因,李三紅的解釋更為詳細,他認為有兩個原因:
敏捷的基礎底層架構對軟件升級的支持,企業對底層架構的重視程度也是 Java 升級的一個很關鍵原因。中國的企業業務發展都很快,但是其實很多對底層架構的支持和重視是不足夠的。底層架構是否在企業內部被統一強管控,是否很容易支持不同軟件版本的灰度,并能通過有效的預發測試,覆蓋軟件升級不兼容等帶來的不確定性,這都考驗著軟件升級的難度。
另外一點,如果企業享受不到技術升級帶來的紅利,包括性能、編程效率等多方面提升,勢必也影響升級的積極性。
從此次 InfoQ 面向開發者的調研來看,對于目前 Java 的新特性和發展方向,56% 的開發者認為可以解決當前的主要業務挑戰,24% 開發者的觀點是不能。這也從另一層面表明:Java 經常被吐槽演進太慢,但是業界對新版本的采用并不十分積極,這可能反映了 Java/JVM 發展與開發者的實際需求存在某種脫節。
OpenJDK 定制版或者公開發行版
由于 Oracle 宣布 2019 年伊始,Oracle JDK 8 以及更?版本在服務器端部署不再免費,因此 OpenJDK 就成為了大多數 Java 用戶的選項。根據《JVM 生態系統報告 2018》調查顯示,70% 的開發者選擇使用 Oracle JDK,21% 的開發者選擇使用 OpenJDK。 陳楚暉也介紹了國內的情況:目前國內開發者使用最多的依舊是 Oracle JDK,其次是 IBM JDK,也有部分企業采用 OpenJDK。
對于 OpenJDK 的技術采用生命周期劃分,專家們有一些觀點上的不一致,楊曉峰認為雖然國內很多頭部廠商都在定制 OpenJDK,但是目前定制 OpenJDK 被采用范圍還都有限,這也跟上文數據結果吻合,所以他會把 OpenJDK 歸在創新者階段。
但是對于參與 OpenJDK 的國內廠商來說,可能看法更加積極。在李三紅看來:廠商是否轉向 OpenJDK,還有一個重要考量因素就是看他們是否愿意付費使用 OracleJDK,如果不是的話,未來 OpenJDK 可能會逐漸取代 Oracle JDK,目前國內頭部廠商都在 OpenJDK 上有所動作,所以他把 OpenJDK 定義在早期大眾階段。阿里巴巴使用并開源了 OpenJDK 長期支持版本 Dragonwell,目前阿里巴巴大部分的應用運行在 Dragonwell 8, 有些已經運行在 Dragonwell 11。
據來自美團的吳革介紹:美團現階段正在測試基于 OpenJDK 的 MtJDK,作為美團 JDK 基礎服務。此外,美團主要會關注 Redhat 和 Amazon 的升級。由于 Azul 沒有公開 OpenJDK 源代碼,所以美團沒有基于 Azul 進行研發。
3. jvm種類, 我們使用的是哪一種,特點?
我們廣泛使用的是HotSpot Vm
Sun Classic Vm
第一款商用的虛擬機,只能使用純解釋器的方式來執行java代碼。已經過時了。
Exact Vm
1)Exact的全稱是Exact Memory Management 準確式內存管理(虛擬機可以知道內存中某個位置的數據是什么內存的)。
2)編譯器和解釋器混合工作以及兩級即時編譯器。
3)只在Solaris平臺發布。還沒在windows即其他平臺上發布,就被HotSpotVm取代。
1)其實是由一家小公司開發的,后臺被sun公司收購了。
2)繼承了1.2款虛擬機的優點外,它還增加了熱點代碼探測技術等其他
3)應用最多!
1)kilobyte簡單,輕量,高度可移植
2)在手機平臺運行(嵌入式領域)
5.JRockit
1)1-4都是sun公司的虛擬機,而JRockit是BEA公司研發的。不過在08年被Oracle收購,后來sun公司也被Oracle公司收購了。
2)世界上最快的java虛擬機。
3)專注服務器端應用。
4)優勢:垃圾收集器;MissionControl服務套件
6.J9
1)IBM公司研發了。它最開始的名字不叫J9,叫IBM Technology for Java virtual Machine ----IT4j
2)類似于HotSpot,他不僅可以用于服務器端,還可以用于桌面應用,嵌入式;它開發是為了IBM產品的各種java平臺
7.Dalvik
1)它不是java虛擬機,因為它沒有遵循java虛擬機的規范,它是不能直接執行編譯后的class文件的
2)它使用的是寄存器架構,而不是常用的棧架構。
3)它所執行的是Dex—dalvik Executalbe文件,這個文件可以通過class文件轉化而來。
4)用于移動端----安卓
8.Microsoft JVM
1)一看就知道是微軟開發的,也是為了自家軟件與java兼容
2)后來被sun公司搞了,現在沒了。。。。。
9.Azul VM 和 Liquid VM(兩款高性能JVM,碾壓HotSpot)
1)像LiquidVM不需要操作系統的支持,它本身就是一個操作系統。我們總是說java慢,是因為運行java代碼時,我們要先進過java虛擬機,再通過虛擬機調操作系統,多了一步。
10.TaobaoVM
1)淘寶根據Hotspot進行深度定制的的虛擬機
2)對硬件的依賴性夠,犧牲了兼容性。
4. 為什么叫java 虛擬機,它與 vmware的區別?
Java虛擬機”(縮寫為JVM)是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。它有自己完善的硬件架構(如處理器、堆棧、寄存器等),還具有相應的指令系統。使用“Java虛擬機”程序就是為了支持與操作系統無關、在任何系統中都可以運行的程序。微軟公司出于競爭策略考慮,在Windows XP中不捆綁JVM,所以只能上網下載。
VM(Virtual Manufacturing ) 主機其實就是VMware主機的簡稱。VM 虛擬制造:其本質是以新產品及其制造系統的全局最優化為目標,以計算機支持的仿真技術為前提,對設計、制造等生產過程進行統一建模,在產品設計階段,實時地、并行地模擬出產品未來制造全過程及其對產品設計的影響,預測產品性能、產品制造成本、產品的可行制造等。
5、java虛擬機的整體架構
?JVM(虛擬機):指以軟件的方式模擬具有完整硬件系統功能、運行在一個完全隔離環境中的完整計算機系統 ,是物理機的軟件實現。常用的虛擬機有VMWare,Virtual Box,Java Virtual Machine
?Java虛擬機陣營:Sun HotSpot VM、BEA JRockit VM、IBM J9 VM、Azul VM、Apache Harmony、Google Dalvik VM、Microsoft JVM…
6. 字節碼的加載流程?
?JVM由三個主要的子系統構成
?類加載器子系統
?運行時數據區(內存結構)
?執行引擎
?Java運行時編譯源碼(.java)成字節碼,由jre運行。jre由java虛擬機(jvm)實現。Jvm分析字節碼,后解釋并執行
.類加載器
負責從文件系統(簡單說就是硬盤)或者網絡上加載class文件,class文 件在文件開頭有特點的標識。
ClassLoader只負責class文件的加載,只要是符合JVM對字節碼文件規范的要求就可以,至于它是否可以運行,由執行引擎決定。
加載的類信息存放在方法區(一塊內存空間),除了類信息之外,方法區中還會存放運行時常量池信息,可能還會包括字符串字面量和數字常量(這部分常量信息是class文件中常量池部分的內存映射)
例如一個磁盤上的xxx.class文件,會被JVM的ClassLoader(有很多類加載器)通過二進制流的方式,加載到JVM中,此時會生成對應的xxx的class對象,通過該class對象就可以調用構造函數生成xxx的對象放在方法區當中。
類加載器加載字節碼文件主要分為三個階段:
加載字節碼文件
通過一個類的路徑,加載此class文件的二進制字節流將這個字節流所代表的靜態存儲結構轉換為方法區(方法區具體來說,jdk7之前叫永久代,之后叫元數據或者元空間,泛稱為方法區)的運行時數據結構
在內存中生成一個代表這個類的java.lang.class對象,作為方法區這個類各種數據的訪問入口
加載字節碼文件的方式:
從系統磁盤直接讀取
通過網絡獲取,如:Web Applet(我沒有了解過~~~)
從zip壓縮包中讀取,典型的就是jar包和war包,這些壓縮包中都是已經編譯好的字節碼文件
運行時生成,如:動態代理(java.lang.reflect)
其他文件生成,典型的形式如:JSP
從專用的數據庫中提取class字節碼文件(我沒有了解過~~~)
鏈接 驗證
確保加載的class字節碼文件內容信息是符合當前JVM規范的,保證被加載類的正確性,不會有安全風險
包括文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證(了解一下就好,如校驗二進制文件頭是否是:CA FE BA BE,CA FE BA BE是JAVA虛擬機識別的標識符)
準備
為類變量分配內存,并且設置變量的初始值(0、false或者null等)
//在鏈接階段的準備過程中,a會被賦值為0,1的賦值會在初始化階段中進行
這里不包含final修飾的static(也就是常量),因為final修飾的常量在編譯的時候就會分配值了,準備階段會顯式的初始化
不會為實例變量初始化(未被static修飾的)成員變量,類變量會被分配到方法區中,實例變量隨對象創建后分配到JVM的堆中
解析
將常量池中的符號引用轉換為直接引用的過程
符號引用就是使用符號來描述所引用的目標(符號引用的字面量形式定義在《Java虛擬機規范》中的class文件格式,可以理解為一個符號從字面上定義了引用的目標),直接引用就是直接指向目標的指針、相對偏移量、或者一個間接定位到的目標句柄
解析的目標主要針對:類、接口、字段、類方法、接口方法、方法類型等。對應常量池當中的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodred_info。(這一塊不是很懂)
初始化
初始化階段就是執行類構造方法()的過程
構造器方法()中的指令,按照源文件中代碼出現的順序執行(其實java的IDE會有相應的語法檢查的,不會讓你在static靜態代碼塊中調用靜態代碼塊之后聲明的變量的)
該方法是定義好的,不是類的構造方法,構造方法對應的class文件中()方法,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合并而來(簡單的理解就是()是在編譯的時候根據類變量賦值操作代碼和類靜態代碼塊中的代碼拼接出來的方法,如果類中沒有定義靜態變量或者是靜態代碼塊那么就不會有()方法產生)
七、 java的編譯器輸入的指令流是一種基于棧的指令集架構, 它有什么優點?
主要特點:
設計實現簡單,適用于資源受限的系統,比如機頂盒,小玩具上。
避開寄存器分配難題:使用零地址指令方式分配。
指令流中大部分都是零地址指令,執行過程依賴操作棧,指令集更小(零地址),編譯器容易實現。
不需要硬件支持,可移植性強,容易實現跨平臺。
八、能運行在虛擬機上的字節碼只能由 javac 編譯而來的java源代碼產生嗎, 除此之外,還有其它哪些語言也可以編譯字節碼出來?
編譯型語言
編譯型語言,在程序執行之前,需要一個專門的編譯鏈接過程,把程序編譯成機器語言文件;比如,exe文件和bin文件。以后運行的話就不用重新編譯了,直接使用編譯的結果就行了。因為翻譯只做了一次,運行時不需要翻譯,所以編譯型語言的程序執行效率高!
??常見的編譯型語言有:C、C++、Pascal、Object Pascal和Delphi等。
九、 能簡單的說說java虛擬機規范嗎?
========================================================
Part Two 類加載相關
1. jvm在什么情況下會加載一個類?
類加載過程
加載——> 驗證 —>準備—> 解析—> 初始化—> 使用—> 卸載
在使用的時候會加載這個類
public class Hello(){public static void mian (){} }解析
解析階段就是把符號引用轉換直接引用
初始化
什么是類初始化?
new getstatic putstatic invokestatic字節指令
反射調用該類
初始化一個類會連帶把父類初始化
執行main方法的類
使用jdk動態語言支持
2. 類加載到jvm中的過程?每個階段的工作?
3. jvm中的類加載器的類型及它加載的目標路徑?如何自定義一個類加載器加載一個指定目錄下的class文件?
1、類加載器
站在Java虛擬機的角度看,只有兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現(HotSpot虛擬機、JDK8中),是虛擬機自身的一部分;另外一種是其他所有類加載器,這些類加載器都由Java語言實現,獨立存在于虛擬機外部,并且全部繼承自抽象類 java.lang.ClassLoader
JDK8及以前版本中絕大多數程序都會使用到以下3個系統提供的類加載器來進行加載
啟動類(引導類)加載器
負責加載支撐JVM運行的位于<JAVA_HOME>\lib目錄下的核心類庫,而且是Java虛擬機能夠識別的類庫加載到虛擬機內存中(如rt.jar、tools.jar、charsets.jar等,名字不符合的類庫即使放到lib目錄下也不會被加載)。
1.引導類加載器使用C/C++語言實現,在JVM內部
2.用于加載Java核心類庫
3.不繼承ClassLoader
4.還用于加載擴展類加載器和應用程序類加載器
5.只加載包名為java,javax,sun開頭的類
擴展類加載器(Extension Class Loader)
這個類加載器是在類sun.misc. $ExtClassLoader中以Java代碼的形式實現的。負責加載<JAVA_HOME>\lib\ext目錄中或被java.ext.dirs系統變量所制定的路徑中所有的類庫,是一種Java系統類庫的擴展機制
1.使用java語言編寫,JVM自帶
2.繼承自ClassLoader
3.父類加載器為啟動類加載器
4.從java.ext.dirs指定的路徑下加載類庫;或者從JDK安裝目錄的jre/lib/ext目錄下加載類庫。
5.如果用戶自定義的jar包放在jre/lib/ext下,也會自動由擴展類加載器加載
應用程序類加載器(Application Class Loader)
是由sun.misc.launcher$AppClassLoader來實現,由于應用程序加載器是ClassLoader類中getSystemClassLoader()方法的返回值,也稱為系統類加載器。負責加載用戶類路徑(ClassPath)上所有的類庫,如應用程序中沒有默認自己的類加載器,則使用應用程序加載器為默認加載器。
自定義加載器:負責加載用戶自定義路徑下的類包
1.使用jaca語言編寫,JVM自帶
2.繼承自ClassLoader
3.父類加載器為擴展類加載器
4.負責加載環境變量classpath或系統屬性java.class.path指定的類庫
5.java中自己寫的類都是由應用程序類加載器加載的
6.可以通過ClassLoader.getSystemClassLoader()方法獲取該類加載器
理解BootstrapClassLoader、ExtClassLoader、AppClassLoader的例子:
public class ClassLoaderTest1 {public static void main(String[] args) {System.out.println("**********啟動類加載器**************");//獲取BootstrapClassLoader能夠加載的api的路徑URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();//獲取到的是通過引導類加載的類庫的路徑(輸出參考“引導類能夠加載的類庫路徑”)for (URL element : urLs) {System.out.println(element.toExternalForm());}//從上面的路徑中隨意選擇一個類,來看看他的類加載器是什么:引導類加載器ClassLoader classLoader = Provider.class.getClassLoader();System.out.println(classLoader); //輸出為null。說明是引導類加載器加載的System.out.println("***********擴展類加載器*************");String extDirs = System.getProperty("java.ext.dirs");for (String path : extDirs.split(";")) {// 輸出參考“擴展類能夠加載的類庫路徑”圖System.out.println(path);}//從上面的路徑中隨意選擇一個類,來看看他的類加載器是什么:擴展類加載器ClassLoader classLoader1 = CurveDB.class.getClassLoader();System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d} }自定義一個類加載器簡單的例子:
public class CustomClassLoader extends ClassLoader { //繼承ClassLoader@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException { //重載findClass方法try {byte[] result = getClassFromCustomPath(name);if(result == null){throw new FileNotFoundException();}else{return defineClass(name,result,0,result.length);}} catch (FileNotFoundException e) {e.printStackTrace();}throw new ClassNotFoundException(name);}private byte[] getClassFromCustomPath(String name){//從自定義路徑中加載指定類:細節略//如果指定路徑的字節碼文件進行了加密,則需要在此方法中進行解密操作。(這里可以進行解密操作,防止class文件被反編譯)return null;}public static void main(String[] args) {CustomClassLoader customClassLoader = new CustomClassLoader();try {Class<?> clazz = Class.forName("One",true,customClassLoader);Object obj = clazz.newInstance();System.out.println(obj.getClass().getClassLoader());} catch (Exception e) {e.printStackTrace();}} }4. 什么是雙親委派模型,有什么作用?
雙親委派模型有兩個好處:
向上委托給父類加載,父類加載不了再自己加載
避免重復加載,防止Java核心api被篡改
加載器自上而下分別為,啟動類加載器(Bootstrap ClassLoader), 拓展類加載器(Extension ClassLoader), 系統類加載器(Application ClassLoader) , 自定義類加載器(Custom ClassLoader)
雙親委派模式是Java1.2之后引入的,其工作原理是,如果其中一個類加載器收到了類加載的請求,它并不會自己去加載而是會將該請求委托給父類的加載器去執行,如果父類加載器還存在父類加載器,則進一步向上委托,如此遞歸,請求最終到達頂層的啟動類加載器。如果父類能加載,則直接返回,如果父類加載不了則交由子類加載,這就是雙親委派模式。
5. 類加載器是如何確定一個類在jvm中的唯一性的? 兩個類來源于同一個Class文件,被同一個虛擬機加載,這兩個類一定相等嗎?
最近遇到了一個問題:由不同類加載器加載同一個類,實例化為對象。使用instanceof判斷該對象與該類的歸屬,請問結果是true還是false? 答案是false。
public class ClassLoaderTest {public static void main(String[] args) throws Exception {ClassLoader myLoader = new ClassLoader() {@SuppressWarnings("ResultOfMethodCallIgnored")@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try {String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream is = getClass().getResourceAsStream(fileName);if (is == null) {return super.loadClass(name);}byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}};Object obj = myLoader.loadClass("jvm.ClassLoaderTest").newInstance();System.out.println(obj.getClass());System.out.println(obj instanceof jvm.ClassLoaderTest);} }對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。這句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。
兩行輸出結果中,從第一句可以看出,這個對象確實是類 Practice.Java.ClassLoaderTest實例化出來的對象,但從第二句可以發現,這個對象與類Practice.Java.ClassLoaderTest做所屬類型檢查的時候卻返回了false。
這是因為虛擬機中存在了兩個ClassLoaderTest類,一個是由系統應用程序類加載器加載的,另外一個是由我們自定義的類加載器加載的。雖然都來自同一個Class文件,但依然是兩個獨立的類,做對象所屬類型檢查時結果自然為false。
結論
Java類加載器這種特性可以簡單的總結為命名空間。即在 Java 虛擬機中,類的唯一性是由類加載器實例以及類的全名一同確定的。即便是同一串字節流,經由不同的類加載器加載,也會得到兩個不同的類。**
6. tomcat的類加載器有哪些?
Tomcat中的類加載器
下面的簡圖是Tomcat9版本的官方文檔給出的Tomcat的類加載器的圖
1.Bootstrap:是java中的最高加載器,用C語言實現,主要用來加載JVM啟動時所需 要的核心類,如:$JAVA_HOME/jre/lib/ext
3.System:會加載CLASSPATH系統變量所定義路徑的所有類
4.Common:會加載Tomcat路徑下的lib文件下的所有類
5.Webapp1,Webapp2????會加載APP路徑下項目中的所有的類,一個項目對應一個6.WabappClassLoader,這樣就實現了應用之間類的隔離
這3個部分,在上面的Java雙親委派模型圖中都有體現。不過可以看到ExtClassLoader沒有畫出來,可以理解為是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加載類。 那么Tomcat為什么要自定義類加載器呢?
隔離不同應用:部署在同一個Tomcat中的不同應用A和B,例如A用了Spring2.5。B用了Spring3.5,那么這兩個應用如果使用的是同一個類加載器,那么Web應用就會因為jar包覆蓋而無法啟動。
靈活性:Web應用之間的類加載器相互獨立,那么就可以根據修改不同的文件重建不同的類加載器替換原來的。從而不影響其他應用。
性能:如果在一個Tomcat部署多個應用,多個應用中都有相同的類庫依賴。那么可以把這相同的類庫讓Common類加載器進行加載。
Tomcat自定義了WebAppClassLoader類加載器
打破了雙親委派的機制,即如果收到類加載的請求,會嘗試自己去加載,如果找不到再交給父加載器去加載,目的就是為了優先加載Web應用自己定義的類。我們知道ClassLoader默認的loadClass方法是以雙親委派的模型進行加載類的,那么Tomcat既然要打破這個規則,就要重寫loadClass方法,我們可以看WebAppClassLoader類中重寫的loadClass方法
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {Class<?> clazz = null;// 1. 從本地緩存中查找是否加載過此類clazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return clazz;}// 2. 從AppClassLoader中查找是否加載過此類clazz = findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return clazz;}String resourceName = binaryNameToPath(name, false);// 3. 嘗試用ExtClassLoader 類加載器加載類,防止Web應用覆蓋JRE的核心類ClassLoader javaseLoader = getJavaseClassLoader();boolean tryLoadingFromJavaseLoader;try {URL url;if (securityManager != null) {PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);url = AccessController.doPrivileged(dp);} else {url = javaseLoader.getResource(resourceName);}tryLoadingFromJavaseLoader = (url != null);} catch (Throwable t) {tryLoadingFromJavaseLoader = true;}boolean delegateLoad = delegate || filter(name, true);// 4. 判斷是否設置了delegate屬性,如果設置為true那么就按照雙親委派機制加載類if (delegateLoad) {if (log.isDebugEnabled())log.debug(" Delegating to parent classloader1 " + parent);try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Loading class from parent");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}// 5. 默認是設置delegate是false的,那么就會先用WebAppClassLoader進行加載if (log.isDebugEnabled())log.debug(" Searching local repositories");try {clazz = findClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Loading class from local repository");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}// 6. 如果此時在WebAppClassLoader沒找到類,那么就委托給AppClassLoader去加載if (!delegateLoad) {if (log.isDebugEnabled())log.debug(" Delegating to parent classloader at end: " + parent);try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Loading class from parent");if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}}throw new ClassNotFoundException(name); }Web應用默認的類加載順序是(打破了雙親委派規則):
先從JVM的BootStrapClassLoader中加載。
加載Web應用下/WEB-INF/classes中的類。
加載Web應用下/WEB-INF/lib/.jap中的jar包中的類。
加載上面定義的System路徑下面的類。
加載上面定義的Common路徑下面的類。
如果在配置文件中配置了,那么就是遵循雙親委派規則,加載順序如下:
. 先從JVM的BootStrapClassLoader中加載。
. 加載上面定義的System路徑下面的類。
. 加載上面定義的Common路徑下面的類。
. 加載Web應用下/WEB-INF/classes中的類。
. 加載Web應用下/WEB-INF/lib/.jap中的jar包中的類
8. 雙親委派模型最大問題:底層的類加載器無法加載底層的類, 比如如下情況:
javax.xml.parsers包中定義了xml解析的類接口, Service Provider Interface SPI 位于rt.jar
即接口在啟動ClassLoader中, 而SPI的實現類,通常是由用戶實現的, 由AppLoader加載。
以下是javax.xmlparsers.FactoryFinder中的解決代碼:
static private Class getProviderClass(String className, ClassLoader cl,boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException {try {if (cl == null) {if (useBSClsLoader) {return Class.forName(className, true, FactoryFinder.class.getClassLoader());} else {cl = ss.getContextClassLoader(); //獲取上下文加載器if (cl == null) {throw new ClassNotFoundException();}else {return cl.loadClass(className); //使用上下文ClassLoader}}}else {return cl.loadClass(className);}}catch (ClassNotFoundException e1) {if (doFallback) {// Use current class loader - should always be bootstrap CLreturn Class.forName(className, true, FactoryFinder.class.getClassLoader());}更多可以參考理解: jdbc的SPI 加載方式. https://blog.csdn.net/syh121/article/details/120274044
ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl);9. 雙親委派模式是默認的模式,但并非必須. 還有以下幾個例 子,它實際上是破壞了雙親委派模式的.
a. Tomcat的WebappClassLoader 就會先加載自己的Class,找不到再委托parent b. OSGi的ClassLoader形成網狀結構,根據需要自由加載Class
10. 請完成一個熱替換的例子,并解釋什么是熱替換?
jvm中只有主動使用類,才會加載類,那么加載類的七種情況有哪些?
類加載過程主要分為三個步驟:加載、鏈接、初始化,而其中鏈接過程又分為三個步驟:驗證、準備、解析,加上卸載、使用兩個步驟統稱為為類的生命周期。
加載
簡單來說,加載指的是把class字節碼文件從各個來源通過類加載器裝載入內存中。
1、字節碼來源
由于沒有具體指明需要在哪里獲取class文件,導致字節碼來源途徑非常豐富:
從壓縮包中讀取,如jar、war
從網絡中獲取,如Web Applet
動態生成,如動態代理、CGLIB
由其他文件生成,如JSP
從數據庫讀取
從加密文件中讀取
2、內存儲存
將靜態儲存解析成運行時數據,存放在方法區
在堆區生成該類的Class對象,作為方法區這個類的各種數據的訪問入口。
驗證
驗證階段主要是為了為了確保Class文件的字節流中包含的信息符合虛擬機要求,并且不會危害虛擬機。
而驗證主要分為以下四類:
文件格式驗證
元數據驗證
字節碼驗證
符號引用驗證
總結
以上是生活随笔為你收集整理的初步了解java虚拟机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10. javacript高级程序设计-
- 下一篇: unity 日志级别_【Unity】通用