日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java虚拟机工作原理详解

發(fā)布時(shí)間:2025/4/9 java 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java虚拟机工作原理详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址:http://blog.csdn.net/bingduanlbd/article/details/8363734

?

一、類加載器

首先來看一下java程序的執(zhí)行過程。

? ? ? ? ? ? ? ? ? ? ? ? ?

從這個(gè)框圖很容易大體上了解java程序工作原理。首先,你寫好java代碼,保存到硬盤當(dāng)中。然后你在命令行中輸入

[java] view plaincopy
  • javac?YourClassName.java??
  • 此時(shí),你的java代碼就被編譯成字節(jié)碼(.class).如果你是在Eclipse IDE或者其他開發(fā)工具中,你保存代碼的時(shí)候,開發(fā)工具已經(jīng)幫你完成了上述的編譯工作,因此你可以在對應(yīng)的目錄下看到class文件。此時(shí)的class文 件依然是保存在硬盤中,因此,當(dāng)你在命令行中運(yùn)行

    [java] view plaincopy
  • java?YourClassName??
  • 就完成了上面紅色方框中的工作。JRE的來加載器從硬盤中讀取class文件,載入到系統(tǒng)分配給JVM的內(nèi)存區(qū)域--運(yùn)行數(shù)據(jù)區(qū)(Runtime Data Areas). 然后執(zhí)行引擎解釋或者編譯類文件,轉(zhuǎn)化成特定CPU的機(jī)器碼,CPU執(zhí)行機(jī)器碼,至此完成整個(gè)過程。


    接下來就重點(diǎn)研究一下類加載器究竟為何物?又是如何工作的?

    首先看一下來加載器的一些特點(diǎn),有點(diǎn)抽象,不過總有幫助的。


    》》層級結(jié)構(gòu)

    類加載器被組織成一種層級結(jié)構(gòu)關(guān)系,也就是父子關(guān)系。其中,Bootstrap是所有類加載器的父親。如下圖所示:

    ? ? ? ??

    --Bootstrap class loader:

    當(dāng)運(yùn)行java虛擬機(jī)時(shí),這個(gè)類加載器被創(chuàng)建,它加載一些基本的java API,包括Object這個(gè)類。需要注意的是,這個(gè)類加載器不是用java語言寫的,而是用C/C++寫的。

    --Extension class loader:

    這個(gè)加載器加載出了基本API之外的一些拓展類,包括一些與安全性能相關(guān)的類。(目前了解得不是很深,只能籠統(tǒng)說,待日后再詳細(xì)說明)

    --System Class Loader:

    它加載應(yīng)用程序中的類,也就是在你的classpath中配置的類。

    --User-Defined Class Loader:

    這是開發(fā)人員通過拓展ClassLoader類定義的自定義加載器,加載程序員定義的一些類。


    》》委派模式(Delegation Mode)

    仔細(xì)看上面的層次結(jié)構(gòu),當(dāng)JVM加載一個(gè)類的時(shí)候,下層的加載器會(huì)將將任務(wù)委托給上一層類加載器,上一層加載檢查它的命名空間中是否已經(jīng)加載這個(gè) 類,如果已經(jīng)加載,直接使用這個(gè)類。如果沒有加載,繼續(xù)往上委托直到頂部。檢查完了之后,按照相反的順序進(jìn)行加載,如果Bootstrap加載器找不到這 個(gè)類,則往下委托,直到找到類文件。對于某個(gè)特定的類加載器來說,一個(gè)Java類只能被載入一次,也就是說在Java虛擬機(jī)中,類的完整標(biāo)識(shí)是 (classLoader,package,className)。一個(gè)雷可以被不同的類加載器加載。


    舉個(gè)具體的例子來說明,現(xiàn)在加入我有一個(gè)自己定義的類MyClass需要加載,如果不指定的話,一般交App(System)加載。接到任務(wù) 后,System檢查自己的庫里是否已經(jīng)有這個(gè)類,發(fā)現(xiàn)沒有之后委托給Extension,Extension進(jìn)行同樣的檢查,發(fā)現(xiàn)還是沒有繼續(xù)往上委 托,最頂層的Boots發(fā)現(xiàn)自己庫里也沒有,于是根據(jù)它的路徑(Java 核心類庫,如java.lang)嘗試去加載,沒找到這個(gè)MaClass類,于是只好(人家看好你,交給你完成,你無能為力,只好交給別人啦)往下委托給 Extension,Extension到自己的路徑(JAVA_HOME/jre/lib/ext)是找,還是沒找到,繼續(xù)往下,此時(shí)System加載 器到classpath路徑尋找,找到了,于是加載到Java虛擬機(jī)。

    現(xiàn)在假設(shè)我們將這個(gè)類放到JAVA_HOME/jre/lib/ext這個(gè)路徑中去(相當(dāng)于交給Extension加載器加載),按照同樣的規(guī)則, 最后由Extension加載器加載MyClass類,看到了吧,統(tǒng)一各類被兩次加載到JVM,但是每次都是由不同的ClassLoader完成。


    》》可見性限制

    下層的加載器能夠看到上層加載器中的類,反之則不行,也就是是說委托只能從下到上。


    》》不允許卸載類

    類加載器可以加載一個(gè)類,但是它不能卸載一個(gè)類。但是類加載器可以被刪除或者被創(chuàng)建。


    當(dāng)類加載完畢之后,JVM繼續(xù)按照下圖完成其他工作:


    框圖中各個(gè)步驟簡單介紹如下:

    Loading:文章前面介紹的類加載,將文件系統(tǒng)中的Class文件載入到JVM內(nèi)存(運(yùn)行數(shù)據(jù)區(qū)域)

    Verifying:檢查載入的類文件是否符合Java規(guī)范和虛擬機(jī)規(guī)范。

    Preparing:為這個(gè)類分配所需要的內(nèi)存,確定這個(gè)類的屬性、方法等所需的數(shù)據(jù)結(jié)構(gòu)。(Prepare a data structure that assigns the memory required by classes and indicates the fields, methods, and interfaces defined in the class.)

    Resolving:將該類常量池中的符號(hào)引用都改變?yōu)橹苯右谩?#xff08;不是很理解)

    Initialing:初始化類的局部變量,為靜態(tài)域賦值,同時(shí)執(zhí)行靜態(tài)初始化塊。


    那么,Class Loader在加載類的時(shí)候,究竟做了些什么工作呢?

    要了解這其中的細(xì)節(jié),必須得先詳細(xì)介紹一下運(yùn)行數(shù)據(jù)區(qū)域。


    二、運(yùn)行數(shù)據(jù)區(qū)域

    Runtime Data Areas:當(dāng)運(yùn)行一個(gè)JVM示例時(shí),系統(tǒng)將分配給它一塊內(nèi)存區(qū)域(這塊內(nèi)存區(qū)域的大小可以設(shè)置的),這一內(nèi)存區(qū)域由JVM自己來管理。從這一塊內(nèi)存中分 出一塊用來存儲(chǔ)一些運(yùn)行數(shù)據(jù),例如創(chuàng)建的對象,傳遞給方法的參數(shù),局部變量,返回值等等。分出來的這一塊就稱為運(yùn)行數(shù)據(jù)區(qū)域。運(yùn)行數(shù)據(jù)區(qū)域可以劃分為6大 塊:Java棧、程序計(jì)數(shù)寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區(qū)域、運(yùn)行常量池(Runtime Constant Pool)。運(yùn)行常量池本應(yīng)該屬于方法區(qū),但是由于其重要性,JVM規(guī)范將其獨(dú)立出來說明。其中,前面3各區(qū)域(PC寄存器、Java棧、本地方法棧)是 每個(gè)線程獨(dú)自擁有的,后三者則是整個(gè)JVM實(shí)例中的所有線程共有的。這六大塊如下圖所示:


    》PC計(jì)數(shù)器:

    每一個(gè)線程都擁有一個(gè)PC計(jì)數(shù)器,當(dāng)線程啟動(dòng)(start)時(shí),PC計(jì)數(shù)器被創(chuàng)建,這個(gè)計(jì)數(shù)器存放當(dāng)前正在被執(zhí)行的字節(jié)碼指令(JVM指令)的地址。

    》Java棧:

    同樣的,Java棧也是每個(gè)線程單獨(dú)擁有,線程啟動(dòng)時(shí)創(chuàng)建。這個(gè)棧中存放著一系列的棧幀(Stack Frame),JVM只能進(jìn)行壓入(Push)和彈出(Pop)棧幀這兩種操作。每當(dāng)調(diào)用一個(gè)方法時(shí),JVM就往棧里壓入一個(gè)棧幀,方法結(jié)束返回時(shí)彈出棧 幀。如果方法執(zhí)行時(shí)出現(xiàn)異常,可以調(diào)用printStackTrace等方法來查看棧的情況。棧的示意圖如下:


    OK。現(xiàn)在我們再來詳細(xì)看看每一個(gè)棧幀中都放著什么東西。從示意圖很容易看出,每個(gè)棧幀包含三個(gè)部分:本地變量數(shù)組,操作數(shù)棧,方法所屬類的常量池引用。

    》局部(本地)變量數(shù)組:

    局部(本地)變量數(shù)組中,從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數(shù)、局部變量。舉個(gè)例子:

    ?

    [java] view plaincopy
  • public?void?doSomething(int?a,?double?b,?Object?o)?{??
  • ...??
  • }??

  • 這個(gè)方法的棧幀中的局部變量存儲(chǔ)的內(nèi)容分別是:

    ?

    [java] view plaincopy
  • 0:?this??
  • 1:?a??
  • 2,3:b??
  • 4:0??

  • 看仔細(xì)了,其中double類型的b需要兩個(gè)連續(xù)的索引。取值的時(shí)候,取出的是2這個(gè)索引中的值。如果是靜態(tài)方法,則數(shù)組第0個(gè)不存放this引用,而是直接存儲(chǔ)傳遞的參數(shù)。

    》操作數(shù)棧:

    操作數(shù)棧中存放方法執(zhí)行時(shí)的一些中間變量,JVM在執(zhí)行方法時(shí)壓入或者彈出這些變量。其實(shí),操作數(shù)棧是方法真正工作的地方,執(zhí)行方法時(shí),局部變量數(shù)組與操作數(shù)棧根據(jù)方法定義進(jìn)行數(shù)據(jù)交換。例如,執(zhí)行以下代碼時(shí),操作數(shù)棧的情況如下:

    ?

    [java] view plaincopy
  • int?a?=?90;??
  • int?b?=?10;??
  • int?c?=?a?+?b;??

  • 注意在這個(gè)圖中,操作數(shù)棧的地步是在上邊,所以先壓入的100位于上方。可以看出,操作數(shù)棧其實(shí)是一個(gè)數(shù)據(jù)臨時(shí)存儲(chǔ)區(qū),存放一些中間變量,方法結(jié)束了,操作數(shù)棧也就沒有啦。

    》棧幀中數(shù)據(jù)引用:

    除了局部變量數(shù)組和操作數(shù)棧之外,棧幀還需要一個(gè)常量池的引用。當(dāng)JVM執(zhí)行到需要常量池的數(shù)據(jù)時(shí),就是通過這個(gè)引用來訪問常量池的。棧幀中的數(shù)據(jù) 還要負(fù)責(zé)處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調(diào)用該方法的方法的 操作數(shù)棧中。另外,數(shù)據(jù)區(qū)中還保存中該方法可能的異常表的引用。下面的例子用來說明:

    [java] view plaincopy
  • class?Example3C{??
  • ????public?static?void?addAndPrint(){??
  • ????????double?result?=?addTwoTypes(1,88.88);??
  • ????????System.out.println(result);??
  • ????}??
  • ????public?static?double?addTwoTypes(int?i,?double?d){??
  • ????return?i+d;??
  • ????}??
  • ??
  • }??

  • 執(zhí)行上述代碼時(shí),Java棧如下圖所示:


    花些時(shí)間好好研究上圖。一樣需要注意的是,棧的底部在上方,先押人員addAndPrint方法的棧幀,再壓入addTwoTypes方法的棧幀。上圖最右邊的文字說明有錯(cuò)誤,應(yīng)該是addTwoTypes的執(zhí)行結(jié)果存放在addAndPrint的操作數(shù)棧中。

    》》本地方法棧

    當(dāng)程序通過JNI(Java Native Interface)調(diào)用本地方法(如C或者C++代碼)時(shí),就根據(jù)本地方法的語言類型建立相應(yīng)的棧。

    》》方法區(qū)域

    方法區(qū)域是一個(gè)JVM實(shí)例中的所有線程共享的,當(dāng)啟動(dòng)一個(gè)JVM實(shí)例時(shí),方法區(qū)域被創(chuàng)建。它用于存運(yùn)行放常量池、有關(guān)域和方法的信息、靜態(tài)變量、類 和方法的字節(jié)碼。不同的JVM實(shí)現(xiàn)方式在實(shí)現(xiàn)方法區(qū)域的時(shí)候會(huì)有所區(qū)別。Oracle的HotSpot稱之為永久區(qū)域(Permanent Area)或者永久代(Permanent Generation)。

    》》運(yùn)行常量池

    這個(gè)區(qū)域存放類和接口的常量,除此之外,它還存放方法和域的所有引用。當(dāng)一個(gè)方法或者域被引用的時(shí)候,JVM就通過運(yùn)行常量池中的這些引用來查找方法和域在內(nèi)存中的的實(shí)際地址。

    》》堆(Heap)

    堆中存放的是程序創(chuàng)建的對象或者實(shí)例。這個(gè)區(qū)域?qū)VM的性能影響很大。垃圾回收機(jī)制處理的正是這一塊內(nèi)存區(qū)域。

    所以,類加載器加載其實(shí)就是根據(jù)編譯后的Class文件,將java字節(jié)碼載入JVM內(nèi)存,并完成對運(yùn)行數(shù)據(jù)處于的初始化工作,供執(zhí)行引擎執(zhí)行。


    三、 執(zhí)行引擎(Execution ?Engine)

    類加載器將字節(jié)碼載入內(nèi)存之后,執(zhí)行引擎以Java 字節(jié)碼指令為但愿,讀取Java字節(jié)碼。問題是,現(xiàn)在的java字節(jié)碼機(jī)器是讀不懂的,因此還必須想辦法將字節(jié)碼轉(zhuǎn)化成平臺(tái)相關(guān)的機(jī)器碼。這個(gè)過程可以由 解釋器來執(zhí)行,也可以有即時(shí)編譯器(JIT Compiler)來完成。


    轉(zhuǎn)載于:https://www.cnblogs.com/qufanblog/p/3844794.html

    總結(jié)

    以上是生活随笔為你收集整理的Java虚拟机工作原理详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。