您好,登錄后才能下訂單哦!
小編給大家分享一下JVM是什么意思,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
在介紹JVM之前,先看一下.java文件從編碼到執行的過程:
整個過程是,x.java文件需要編譯成x.class文件,通過類加載器加載到內存中,然后通過解釋器或者即時編譯器進行解釋和編譯,最后交給執行引擎執行,執行引擎操作OS硬件。
從類加載器到執行引擎這塊內容就是JVM。
JVM是一個跨語言的平臺。從上面的圖中可以看到,實際上JVM上運行的不是.java文件,而是.class文件。這就引出一個觀點,JVM是一個跨語言的平臺,他不僅僅能跑java程序,只要這種編程語言能編譯成JVM可識別的.class文件都可以在上面運行。
所以除了java以外,能在JVM上運行的語言有很多,比如JRuby、Groovy、Scala、Kotlin等等。
從本質上講JVM就是一臺通過軟件虛擬的計算機,它有它自身的指令集,有它自身的操作系統。
所以Oracle給JVM定了一套JVM規范,Oracle公司也給出了他的實現。基本上是目前最多人使用的java虛擬機實現,叫做Hotspot。使用java -version可以查看:
一些體量較大,有一定規模的公司,也會開發自己的JVM虛擬機,比如淘寶的TaobaoVM、IBM公司的J9-IBM、微軟的MicrosoftVM等等。
JVM應該很清楚了,是運行.class文件的虛擬機。JRE則是運行時環境,包括JVM和java核心類庫,沒有核心的類庫是跑不起來的。
JDK則包括JRE和一些開發使用的工具集。
所以總的關系是JDK > JRE > JVM。
類加載是JVM工作的一個很重要的過程,我們知道.class是存在在硬盤上的一個文件,如何加載到內存工作的呢,面試中也經常問這個問題。所以你要和其他程序員拉開差距,體現差異化,這個問題要搞懂。
類加載的過程實際上分為三大步:Loading(加載)、Linking(連接)、Initlalizing(初始化)。
其中第二步Linking又分為三小步:Verification(驗證)、Preparation(準備)、Resolution(解析)。
Loading是把.class字節碼文件加載到內存中,并將這些數據轉換成方法區中的運行時數據,在堆中生成一個java.lang.Class類對象代表這個類,作為方法區這些類型數據的訪問入口。
Linking簡單來說,就是把原始的類定義的信息合并到JVM運行狀態之中。分為三小步進行。
驗證加載的類信息是否符合class文件的標準,防止惡意信息或者不符合規范的字節信息。是JVM虛擬機運行安全的重要保障。
創建類或者接口中的靜態變量,并初始化靜態變量賦默認值。賦默認值不是賦初始值,比如static int i = 5,這一步只是把i賦值為0,而不是賦值為5。賦值為5是在后面的步驟。
把class文件常量池里面用到的符號引用轉換成直接內存地址,直接可以訪問到的內容。
這一步真正去執行類初始化clinit()(類構造器)的代碼邏輯,包括靜態字段賦值的動作,以及執行類定義中的靜態代碼塊內(static{})的邏輯。當初始化一個類時,發現父類還沒有進行過初始化,則先初始化父類。虛擬機會保證一個類的clinit()方法在多線程環境中被正確加鎖和同步。
上面就是類加載的整個過程。而最后一步Initlalizing是通過類加載器加載類。類加載器這里我單獨講一下,因為這是一個重點。
Java中的類加載器由上到下分為:
從類圖,可以看到ExtClassLoader和AppClassLoader都是ClassLoader的子類。
所以如果要自定義一個類加載器,可以繼承ClassLoader抽象類,重寫里面的方法。重寫什么方法后面再講。
講完類加載器,這些類加載器是怎么工作的呢。對于雙親委派機制可能多多少少有聽過,沒聽過也沒關系,我正要講。
上面說過有Bootstrap,ExtClassLoader,AppClassLoader三個類加載器。工作機制如下:
加載類的邏輯是怎么樣的呢,核心代碼是可以在JDK源碼中找到的,在抽象類ClassLoader類的loadClass(),有興趣可以源碼看看:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果上層有類加載器,遞歸向上,往上層的類加載器尋找
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果上層的都找不到相應的class
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//自己去加載
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
其實整個邏輯已經很清晰了,為了更好理解,我這里畫張圖給給大家,更好理解一點:
看到這里,應該都清楚了雙親委派機制的流程了。重點來了,為什么要使用雙親委派機制呢?
如果面試官問這個問題,一定要答出關鍵字:安全性。
反證法來辯證。假設不采用雙親委派機制,那我可以自定義一個類加載器,然后我寫一個java.lang.String類用自定義的類加載器加載進去,原來java本身又有一個java.lang.String類,那么類的唯一性就沒法保證,就不就給虛擬機的安全帶來的隱患了嗎。所以要保證一個類只能由同一個類加載器加載,才能保證系統類的的安全。
自定義類加載器,上面講過可以有樣學樣,自定義一個類繼承ClassLoader抽象類。重寫哪個方法呢?loadClass()方法是加載類的方法,重寫這個不就行了?
如果重寫loadClass()那證明有思考過,但是不太對,因為重寫loadClass()會破壞了雙親委派機制的邏輯。應該重寫loadClass()方法里的findClass()方法。
findClass()方法才是自定義類加載器加載類的方法。
那findClass()方法源碼是怎么樣的呢?
明顯這個方法是給子類重寫用的,權限修飾符也是protected,如果不重寫,那就會拋出找不到類的異常。如果學過設計模式的同學,應該看得出來這里用了模板模式的設計模式。所以我們自定義類加載器重寫此方法即可。開始動手!
創建CustomerClassLoader類,繼承ClassLoader抽象類的findClass()方法。
public class CustomerClassLoader extends ClassLoader {
//class文件在磁盤中的路徑
private String path;
//通過構造器初始化class文件的路徑
public CustomerClassLoader(String path) {
this.path = path;
}
/**
* 加載類
*
* @param name 類的全路徑
* @return Class<?>
* @author Ye hongzhi
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
//獲取class文件,轉成字節碼數組
byte[] data = getData();
if (data != null) {
//將class的字節碼數組轉換成Class類的實例
clazz = defineClass(name, data, 0, data.length);
}
//返回Class對象
return clazz;
}
private byte[] getData() {
File file = new File(path);
if (file.exists()) {
try (FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();) {
byte[] buffer = new byte[1024];
int size;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
}
這樣就完成了,接下來測試一下,定義一個Hello類。
public class Hello {
public void say() {
System.out.println("hello.......java");
}
}
使用javac命令編譯成class文件,如下圖:
最后寫個main方法運行測試一把:
public class Main {
public static void main(String[] args) throws Exception {
String path = "D:\\mall\\core\\src\\main\\java\\io\\github\\yehongzhi\\classloader\\Hello.class";
CustomerClassLoader classLoader = new CustomerClassLoader(path);
Class<?> clazz = classLoader.findClass("io.github.yehongzhi.classloader.Hello");
System.out.println("使用類加載器:" + clazz.getClassLoader());
Method method = clazz.getDeclaredMethod("say");
Object obj = clazz.newInstance();
method.invoke(obj);
}
}
運行結果:
看到這里,你肯定會很疑惑。上面不是才講過雙親委派機制為了保證系統的安全性嗎,為什么又要破壞雙親委派機制呢?
重溫一下雙親委派機制,應該還記得,就是底層的類加載器一直委托上層的類加載器,如果上層的已經加載了,就無需加載,上層的類加載器沒有加載則自己加載。這就突出了雙親委派機制的一個缺陷,就是只能子的類加載器委托父的類加載器,不能反過來用父的類加載器委托子的類加載器。
那你會問,什么情況會出現父的類加載器委托子的類加載器呢?
還真有這個場景,就是加載JDBC的數據庫驅動。在JDK中有一個所有 JDBC 驅動程序需要實現的接口Java.sql.Driver。而Driver接口的實現類則是由各大數據庫廠商提供。那問題就出現了,DriverManager(JDK的rt.jar包中)要加載各個實現了Driver接口的實現類,然后進行統一管理,但是DriverManager是由Bootstrap類加載器加載的,只能加載JAVA_HOME下lib目錄下的文件(可以看回上面雙親委派機制的第一張圖),但是實現類是服務商提供的,由AppClassLoader加載,這就需要Bootstrap(上層類加載器)委托AppClassLoader(下層類加載器),也就破壞了雙親委派機制。這只是其中一種場景,破壞雙親委派機制的例子還有很多。
那么怎么實現破壞雙親委派機制呢?
那么剛剛說的JDBC又是采用什么方式破壞雙親委派機制的呢?
當然是采用上下文文件類加載器,還有使用了SPI機制,下面一步一步分解。
第一步,Bootstrap加載DriverManager類,在DriverManager類的靜態代碼塊調用初始化方法。
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
第二步,加載Driver接口的所有實現類,得到Driver實現類的集合,獲取一個迭代器。
第三步,看ServiceLoader.load()方法。
第四步,看迭代器driversIterator。
接著一直找下去,就會看到一個很神奇的地方。
而這個常量值PREFIX則是:
private static final String PREFIX = "META-INF/services/";
所以我們可以在mysql驅動包中找到這個文件:
通過文件名找接口的實現類,這是java的SPI機制。到此為止,破案了大人!
作為暖男的我,就畫張圖,總結一下整個過程吧:
以上是“JVM是什么意思”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。