溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JVM是什么意思

發(fā)布時(shí)間:2021-12-24 09:55:02 來源:億速云 閱讀:146 作者:小新 欄目:大數(shù)據(jù)

小編給大家分享一下JVM是什么意思,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!


 

一、JVM介紹

在介紹JVM之前,先看一下.java文件從編碼到執(zhí)行的過程:

JVM是什么意思  

整個(gè)過程是,x.java文件需要編譯成x.class文件,通過類加載器加載到內(nèi)存中,然后通過解釋器或者即時(shí)編譯器進(jìn)行解釋和編譯,最后交給執(zhí)行引擎執(zhí)行,執(zhí)行引擎操作OS硬件。

類加載器到執(zhí)行引擎這塊內(nèi)容就是JVM。

JVM是一個(gè)跨語言的平臺(tái)。從上面的圖中可以看到,實(shí)際上JVM上運(yùn)行的不是.java文件,而是.class文件。這就引出一個(gè)觀點(diǎn),JVM是一個(gè)跨語言的平臺(tái),他不僅僅能跑java程序,只要這種編程語言能編譯成JVM可識(shí)別的.class文件都可以在上面運(yùn)行。

所以除了java以外,能在JVM上運(yùn)行的語言有很多,比如JRuby、Groovy、Scala、Kotlin等等。

從本質(zhì)上講JVM就是一臺(tái)通過軟件虛擬的計(jì)算機(jī),它有它自身的指令集,有它自身的操作系統(tǒng)。

所以O(shè)racle給JVM定了一套JVM規(guī)范,Oracle公司也給出了他的實(shí)現(xiàn)?;旧鲜悄壳白疃嗳耸褂玫膉ava虛擬機(jī)實(shí)現(xiàn),叫做Hotspot。使用java -version可以查看:

JVM是什么意思  

一些體量較大,有一定規(guī)模的公司,也會(huì)開發(fā)自己的JVM虛擬機(jī),比如淘寶的TaobaoVM、IBM公司的J9-IBM、微軟的MicrosoftVM等等。

 

二、JDK、JRE、JVM

JVM是什么意思  

JVM應(yīng)該很清楚了,是運(yùn)行.class文件的虛擬機(jī)。JRE則是運(yùn)行時(shí)環(huán)境,包括JVM和java核心類庫,沒有核心的類庫是跑不起來的。

JVM是什么意思  

JDK則包括JRE和一些開發(fā)使用的工具集。

所以總的關(guān)系是JDK > JRE > JVM。

 

三、Class加載過程

類加載是JVM工作的一個(gè)很重要的過程,我們知道.class是存在在硬盤上的一個(gè)文件,如何加載到內(nèi)存工作的呢,面試中也經(jīng)常問這個(gè)問題。所以你要和其他程序員拉開差距,體現(xiàn)差異化,這個(gè)問題要搞懂。

類加載的過程實(shí)際上分為三大步:Loading(加載)、Linking(連接)、Initlalizing(初始化)。

其中第二步Linking又分為三小步:Verification(驗(yàn)證)、Preparation(準(zhǔn)備)、Resolution(解析)。

JVM是什么意思  
 

3.1 Loading

Loading是把.class字節(jié)碼文件加載到內(nèi)存中,并將這些數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù),在堆中生成一個(gè)java.lang.Class類對(duì)象代表這個(gè)類,作為方法區(qū)這些類型數(shù)據(jù)的訪問入口。

 

3.2 Linking

Linking簡單來說,就是把原始的類定義的信息合并到JVM運(yùn)行狀態(tài)之中。分為三小步進(jìn)行。

 

3.2.1 Verification

驗(yàn)證加載的類信息是否符合class文件的標(biāo)準(zhǔn),防止惡意信息或者不符合規(guī)范的字節(jié)信息。是JVM虛擬機(jī)運(yùn)行安全的重要保障。

 

3.2.2 Preparation

創(chuàng)建類或者接口中的靜態(tài)變量,并初始化靜態(tài)變量賦默認(rèn)值。賦默認(rèn)值不是賦初始值,比如static int i = 5,這一步只是把i賦值為0,而不是賦值為5。賦值為5是在后面的步驟。

 

3.2.3 Resolution

把class文件常量池里面用到的符號(hào)引用轉(zhuǎn)換成直接內(nèi)存地址,直接可以訪問到的內(nèi)容。

 

3.3 Initlalizing

這一步真正去執(zhí)行類初始化clinit()(類構(gòu)造器)的代碼邏輯,包括靜態(tài)字段賦值的動(dòng)作,以及執(zhí)行類定義中的靜態(tài)代碼塊內(nèi)(static{})的邏輯。當(dāng)初始化一個(gè)類時(shí),發(fā)現(xiàn)父類還沒有進(jìn)行過初始化,則先初始化父類。虛擬機(jī)會(huì)保證一個(gè)類的clinit()方法在多線程環(huán)境中被正確加鎖和同步。

 

四、類加載器

上面就是類加載的整個(gè)過程。而最后一步Initlalizing是通過類加載器加載類。類加載器這里我單獨(dú)講一下,因?yàn)檫@是一個(gè)重點(diǎn)。

Java中的類加載器由上到下分為:

  • Bootstrap ClassLoader(啟動(dòng)類加載器)
  • ExtClassLoader(擴(kuò)展類加載器)
  • AppClassLoader(應(yīng)用程序類加載器)

從類圖,可以看到ExtClassLoader和AppClassLoader都是ClassLoader的子類

JVM是什么意思  

所以如果要自定義一個(gè)類加載器,可以繼承ClassLoader抽象類,重寫里面的方法。重寫什么方法后面再講。

 

五、雙親委派機(jī)制

講完類加載器,這些類加載器是怎么工作的呢。對(duì)于雙親委派機(jī)制可能多多少少有聽過,沒聽過也沒關(guān)系,我正要講。

上面說過有Bootstrap,ExtClassLoader,AppClassLoader三個(gè)類加載器。工作機(jī)制如下:

JVM是什么意思  

加載類的邏輯是怎么樣的呢,核心代碼是可以在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
            }
   //如果上層的都找不到相應(yīng)的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;
    }
}
 

其實(shí)整個(gè)邏輯已經(jīng)很清晰了,為了更好理解,我這里畫張圖給給大家,更好理解一點(diǎn):

JVM是什么意思  

看到這里,應(yīng)該都清楚了雙親委派機(jī)制的流程了。重點(diǎn)來了,為什么要使用雙親委派機(jī)制呢?

如果面試官問這個(gè)問題,一定要答出關(guān)鍵字:安全性。

反證法來辯證。假設(shè)不采用雙親委派機(jī)制,那我可以自定義一個(gè)類加載器,然后我寫一個(gè)java.lang.String類用自定義的類加載器加載進(jìn)去,原來java本身又有一個(gè)java.lang.String類,那么類的唯一性就沒法保證,就不就給虛擬機(jī)的安全帶來的隱患了嗎。所以要保證一個(gè)類只能由同一個(gè)類加載器加載,才能保證系統(tǒng)類的的安全

 

六、自定義類加載器

自定義類加載器,上面講過可以有樣學(xué)樣,自定義一個(gè)類繼承ClassLoader抽象類。重寫哪個(gè)方法呢?loadClass()方法是加載類的方法,重寫這個(gè)不就行了?

如果重寫loadClass()那證明有思考過,但是不太對(duì),因?yàn)橹貙憀oadClass()會(huì)破壞了雙親委派機(jī)制的邏輯。應(yīng)該重寫loadClass()方法里的findClass()方法。

findClass()方法才是自定義類加載器加載類的方法。

JVM是什么意思  

那findClass()方法源碼是怎么樣的呢?

JVM是什么意思  

明顯這個(gè)方法是給子類重寫用的,權(quán)限修飾符也是protected,如果不重寫,那就會(huì)拋出找不到類的異常。如果學(xué)過設(shè)計(jì)模式的同學(xué),應(yīng)該看得出來這里用了模板模式的設(shè)計(jì)模式。所以我們自定義類加載器重寫此方法即可。開始動(dòng)手!

創(chuàng)建CustomerClassLoader類,繼承ClassLoader抽象類的findClass()方法。

public class CustomerClassLoader extends ClassLoader {
 //class文件在磁盤中的路徑
    private String path;
 //通過構(gòu)造器初始化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文件,轉(zhuǎn)成字節(jié)碼數(shù)組
        byte[] data = getData();
        if (data != null) {
            //將class的字節(jié)碼數(shù)組轉(zhuǎn)換成Class類的實(shí)例
            clazz = defineClass(name, data, 0, data.length);
        }
        //返回Class對(duì)象
        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;
        }
    }
}
 

這樣就完成了,接下來測試一下,定義一個(gè)Hello類。

public class Hello {
    public void say() {
        System.out.println("hello.......java");
    }
}
 

使用javac命令編譯成class文件,如下圖:

JVM是什么意思  

最后寫個(gè)main方法運(yùn)行測試一把:

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);
    }
}
 

運(yùn)行結(jié)果:

JVM是什么意思  
 

七、破壞雙親委派機(jī)制

看到這里,你肯定會(huì)很疑惑。上面不是才講過雙親委派機(jī)制為了保證系統(tǒng)的安全性嗎,為什么又要破壞雙親委派機(jī)制呢?

重溫一下雙親委派機(jī)制,應(yīng)該還記得,就是底層的類加載器一直委托上層的類加載器,如果上層的已經(jīng)加載了,就無需加載,上層的類加載器沒有加載則自己加載。這就突出了雙親委派機(jī)制的一個(gè)缺陷,就是只能子的類加載器委托父的類加載器,不能反過來用父的類加載器委托子的類加載器

那你會(huì)問,什么情況會(huì)出現(xiàn)父的類加載器委托子的類加載器呢?

還真有這個(gè)場景,就是加載JDBC的數(shù)據(jù)庫驅(qū)動(dòng)。在JDK中有一個(gè)所有 JDBC 驅(qū)動(dòng)程序需要實(shí)現(xiàn)的接口Java.sql.Driver。而Driver接口的實(shí)現(xiàn)類則是由各大數(shù)據(jù)庫廠商提供。那問題就出現(xiàn)了,DriverManager(JDK的rt.jar包中)要加載各個(gè)實(shí)現(xiàn)了Driver接口的實(shí)現(xiàn)類,然后進(jìn)行統(tǒng)一管理,但是DriverManager是由Bootstrap類加載器加載的,只能加載JAVA_HOME下lib目錄下的文件(可以看回上面雙親委派機(jī)制的第一張圖),但是實(shí)現(xiàn)類是服務(wù)商提供的,由AppClassLoader加載,這就需要Bootstrap(上層類加載器)委托AppClassLoader(下層類加載器),也就破壞了雙親委派機(jī)制。這只是其中一種場景,破壞雙親委派機(jī)制的例子還有很多。

那么怎么實(shí)現(xiàn)破壞雙親委派機(jī)制呢?

  • 最簡單就是自定義類加載器,前面講過為了不破壞雙親委派機(jī)制重寫findClass()方法,所以如果我要破壞雙親委派機(jī)制,那就重寫loadClass()方法,直接把雙親委派機(jī)制的邏輯給改了。在JDK1.2后不提倡重寫此方法。所以提供下面這種方式。
  • 使用線程上下文件類加載器(Thread Context ClassLoader)。     這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè);如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過,那么這個(gè)類加載器默認(rèn)就是AppClassLoader類加載器。
JVM是什么意思  

那么剛剛說的JDBC又是采用什么方式破壞雙親委派機(jī)制的呢?

當(dāng)然是采用上下文文件類加載器,還有使用了SPI機(jī)制,下面一步一步分解。

第一步,Bootstrap加載DriverManager類,在DriverManager類的靜態(tài)代碼塊調(diào)用初始化方法。

public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}
 

第二步,加載Driver接口的所有實(shí)現(xiàn)類,得到Driver實(shí)現(xiàn)類的集合,獲取一個(gè)迭代器。

JVM是什么意思  

第三步,看ServiceLoader.load()方法。

JVM是什么意思  
JVM是什么意思  
JVM是什么意思  

第四步,看迭代器driversIterator。

JVM是什么意思  

接著一直找下去,就會(huì)看到一個(gè)很神奇的地方。

JVM是什么意思  

而這個(gè)常量值PREFIX則是:

private static final String PREFIX = "META-INF/services/";
 

所以我們可以在mysql驅(qū)動(dòng)包中找到這個(gè)文件:

JVM是什么意思  

通過文件名找接口的實(shí)現(xiàn)類,這是java的SPI機(jī)制。到此為止,破案了大人!

作為暖男的我,就畫張圖,總結(jié)一下整個(gè)過程吧:

JVM是什么意思  
 

以上是“JVM是什么意思”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

jvm
AI