歡迎你們關注 github.com/hsfxuebao/j… ,但願對你們有所幫助,要是以爲能夠的話麻煩給點一下Star哈html
轉自:www.cnblogs.com/xjwhaha/p/1…java
jvm運行,有哪些重要的 組件,以下圖git
共可分紅三個大類github
下面挨個說明數據庫
當咱們把 代碼寫完,編譯成字節碼 class文件後,打包運行,接下來就是jvm的工做了,jvm經過類加載器ClassLoader完成 類加載的過程,bootstrap
class file存在於本地硬盤上,能夠理解爲設計師畫在紙上的模板,而最終這個模板在執行的時候是要加載到JVM當中來 , 根據這個文件實例化出n個如出一轍的實例。而這個模板就是咱們使用反射時常常看到的東西,Class對象,每一個類都對應一個此對象,存放類的元數據。api
示意圖:數組
整個類加載過程 可分紅三個部分,加載,連接(驗證 --> 準備 --> 解析) ,初始化安全
加載class文件的方式可有如下幾種markdown
連接分爲三個子階段:驗證 --> 準備 --> 解析
驗證(Verify):
目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
例如:字節碼文件(BinaryViewer查看),其開頭均爲 CAFE BABE ,若是出現不合法的字節碼文件,那麼將會驗證不經過
準備(Prepare)
舉例:
public class HelloApp {
private static int a = 1; //變量a在準備階段會賦初始值,但不是1,而是0,在初始化階段會被賦值爲 1
public static void main(String[] args) {
System.out.println(a);
}
}
複製代碼
解析(Resolve)
舉例:
public class ClinitTest {
private int a = 1;
private static int c = 3;
public static void main(String[] args) {
int b = 2;
}
}
複製代碼
類中有靜態變量生成 clinit 方法
若沒有static變量
public class ClinitTest {
private int a = 1;
public static void main(String[] args) {
int b = 2;
}
}
複製代碼
則沒有clinit方法
舉例:
public class ClassInitTest {
private static int number = 10; //linking之prepare: number = 0 --> initial: 10 --> 20
static {
number = 20;
System.out.println(num);
}
public static void main(String[] args) {
System.out.println(ClassInitTest.num);//2
System.out.println(ClassInitTest.number);//10
}
}
複製代碼
靜態變量 number 的值變化過程以下
<clinit>()
不一樣於類的構造器。(關聯:構造器是虛擬機視角下的<init>()
,)<clinit>()
執行前,父類的<clinit>()
已經執行完畢<clinit>()
方法在多線程下被同步加鎖(多個線程同時加載同一個類)JVM支持兩種類型的類加載器 。分別爲引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader )
從概念上來說,自定義類加載器通常指的是程序中由開發人員自定義的一類類加載器,可是Java虛擬機規範卻沒有這麼定義,而是將全部派生於抽象類ClassLoader的類加載器都劃分爲自定義類加載器
不管類加載器的類型如何劃分,在程序中咱們最多見的類加載器始終只有3個,這裏的四者之間是包含關係,不是上層和下層,也不是子父類的繼承關係。 以下所示
代碼:
public class ClassLoaderTest {
public static void main(String[] args) {
//獲取系統類加載器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//獲取其上層:擴展類加載器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//獲取其上層:獲取不到引導類加載器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//對於用戶自定義類來講:默認使用系統類加載器進行加載
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String類使用引導類加載器進行加載的。---> Java的核心類庫都是使用引導類加載器進行加載的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
複製代碼
經常使用加載器介紹:
啓動類加載器(引導類加載器,Bootstrap ClassLoader)
擴展類加載器(Extension ClassLoader)
應用程序類加載器(系統類加載器,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
}
}
複製代碼
打印結果
String 類就在 其中的rt.jar中, 由 啓動類加載器加載
擴展類加載器加載 lib下的 ext下的jar包類
**********啓動類加載器**************
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/classes
null
***********擴展類加載器*************
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@7ea987ac
複製代碼
注意,此爲用戶自定義,不是jvm規範中的 自定義 加載器,此處概述 ,後面詳細介紹
爲何須要自定義類加載器?
如何自定義類加載器?
ClassLoader類,它是一個抽象類,其後全部的類加載器都繼承自ClassLoader(不包括啓動類加載器)而且都定義在sun.misc.Launcher 類中,都是其子類,sun.misc.Launcher 它是一個java虛擬機的入口應用
經常使用方法:
方法名稱
描述
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類
獲取ClassLoader 實例的途徑
代碼演示:
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.Class.forName().getClassLoader()
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader); // String 類由啓動類加載器加載,咱們沒法獲取
//2.Thread.currentThread().getContextClassLoader()
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3.ClassLoader.getSystemClassLoader().getParent()
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 輸出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
複製代碼
Java虛擬機對class文件採用的是按需加載的方式,也就是說當須要使用該類時纔會將它的class文件加載到內存生成class對象。並且加載某個類的class文件時,Java虛擬機採用的是雙親委派模式,即把請求交由父類處理(父類再向上委派,一直到啓動類加載器),它是一種任務委派模式
舉例:
本身創建一個java.lang.String類,若是此類加載 將打印 語句
package java.lang;
public class String {
static{
System.out.println("我是自定義的String類的靜態代碼塊");
}
}
複製代碼
在另外的程序中加載 String 類,上面的語句並無打印,默認加載就是 原生的String類
public class StringTest {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("hello,atguigu.com");
StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
複製代碼
由於 當程序須要加載 java.lang.String 類時,類加載器將一直向上委託, 直到 啓動器加載類,而啓動器加載能夠加載String 類,則爲 原生java.lang下的String 類,這將保證原生api的安全性,不會由於用戶環境重寫的類,致使全部之前使用原生api的地方受到干擾
代碼2
package java.lang;
public class String {
static{
System.out.println("我是自定義的String類的靜態代碼塊");
}
//錯誤: 在類 java.lang.String 中找不到 main 方法
public static void main(String[] args) {
System.out.println("hello,String");
}
}
複製代碼
運行時報錯:
當加載main方法時, 加載其承載類String,一樣委託到啓動器加載類,致使加載成原生的String 類,而原生的String 類可沒有 main方法
代碼3
在自定義的java.lang 包下定義自定義的類
package java.lang;
public class ShkStart {
public static void main(String[] args) {
System.out.println("hello!");
}
}
複製代碼
運行報錯
由於報名爲java.lang 屬於啓動類加載器的加載範疇, 出於對 啓動類加載器的保護,jvm禁止 自定義的類被 啓動類加載器加載,防止自定義類 對啓動器加載器產生破壞,
經過上面的例子,咱們能夠知道,雙親機制能夠
當咱們使用 jdbc 等 第三方 實現類jar包時,使用到ClassLoader
自定義String類時:在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程當中會先加載jdk自帶的文件(rt.jar包中java.lang.String.class),報錯信息說沒有main方法,就是由於加載的是rt.jar包中的String類。這樣能夠對java核心源代碼的保護,保證java核心代碼的運行環境絕對的獨立,這就是沙箱安全機制
相關問題:
如何判斷兩個class對象是否相同?
在JVM中表示兩個class對象是否爲同一個類存在兩個必要條件:
類的主動使用和被動使用
Java程序對類的使用方式分爲:主動使用和被動使用。主動使用,又分爲七種狀況:
除了以上七種狀況,其餘使用Java類的方式都被看做是對類的被動使用,都不會致使類的初始化,即不會執行初始化階段(不會調用 clinit() 方法和 init() 方法)