新聞中心
描述java虛擬機加載類的過程,要詳盡!
先笑搏禪將銀賀class文件讀到JVM,找到static
專注于為中小企業(yè)提供成都網(wǎng)站制作、成都網(wǎng)站設(shè)計、外貿(mào)營銷網(wǎng)站建設(shè)服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)哈密免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
的變量并初始化,在找到static代碼段執(zhí)行,在初始化普通變量,然后是構(gòu)造方法。最后載碰塵入其他方法。
什么是Java虛擬機?
虛擬機是一種抽象化的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。
Java虛擬機有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息,使得Java程序只需生成在Java虛擬機上運行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。
這種解釋應(yīng)該算是正確的,但是只描述了虛擬機的外部行為和功能,并沒有針對內(nèi)部原理做出說明。搜段薯一般情況下我們不需要知道虛擬機的運行原理,只要專注寫java代碼就可以了,這也正是虛擬機之所以存在的原因--屏蔽底層操作系統(tǒng)平臺的不同并且減少基于原生語言開發(fā)的復(fù)雜性,使java這門語言能夠跨各種平臺(只要虛擬機廠商在特定平臺上實現(xiàn)了虛擬機),并且簡單易用。這些都是虛擬機的外部特性,但是從這些信息來解釋虛擬機,未免太籠統(tǒng)了,無法讓我們知道內(nèi)部原理。
從進(jìn)程的角度解釋JVM
讓我們嘗試從操作系統(tǒng)的層面來理解虛擬機。我們知道,虛擬機是運行在操作系統(tǒng)之中的,那么什么東西才能在操作系統(tǒng)中運行呢?當(dāng)然是進(jìn)程,因為進(jìn)程是操作系統(tǒng)中的執(zhí)行單位??梢赃@樣理解,當(dāng)它在運行的時候,它就是一個操作系統(tǒng)中的進(jìn)程實例,當(dāng)它沒有在運行時(作為可執(zhí)行文件存放于文件系統(tǒng)中),可以把它叫做程序。
對命令行比較熟悉的同學(xué),都知道其實一個命令對應(yīng)一個可執(zhí)行的二進(jìn)制文件,當(dāng)敲下這個命令并且回車后,就會創(chuàng)建一個進(jìn)程,加載對應(yīng)的可執(zhí)行文件到進(jìn)程的地址空間中,并且執(zhí)行其中的指令。下面對比C語言和Java語言的HelloWorld程序來說明問題。
首先編寫C語言版的HelloWorld程序。
編譯C語言版的HelloWorld程序:
gcc HelloWorld.c -o HelloWorld
運行C語言版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloWorld/src$ ./HelloWorld
hello world
gcc編譯器編譯后的文件直接就是可被操作系統(tǒng)識別的二進(jìn)制可執(zhí)行文件,當(dāng)我們在命令行中敲下 ./HelloWorld這條命令的時候, 直接創(chuàng)建一個進(jìn)程, 并且將可執(zhí)行文件加載到進(jìn)程的地址空間中, 執(zhí)行文件中的指令。
作為對比, 我們看一下Java版HelloWord程序的編譯和執(zhí)行形式。
首先編寫源文件HelloWord.java :
編譯Java版的HelloWorld程序:
運行Java版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloJava/src$ java -classpath . HelloWorld
HelloWorld
從上面的過程可以看到, 我們在運行Java版的HelloWorld程序的時燃槐候, 敲入的命令并不是 ./HelloWorld.class 。 因為class文件并不是可以直接被操作系統(tǒng)識別的二進(jìn)制可執(zhí)行文件 。 我們敲入的是java這個命令。 這個命令說明, 我們首先啟動的是一個叫做java的程序, 這個java程序在運行起來之后就是一個JVM進(jìn)程實例。
上面的命令執(zhí)行流程是這樣的:
java命令首先啟動虛擬機進(jìn)程,虛擬機進(jìn)程成功啟動后,讀取參數(shù)“HelloWorld”,把他作為初始類加載到內(nèi)存,對這個類進(jìn)行初始化和動態(tài)鏈接(關(guān)于類的初始化和世者動態(tài)鏈接會在后面的博客中介紹),然后從這個類的main方法開始執(zhí)行。也就是說我們的.class文件不是直接被系統(tǒng)加載后直接在cpu上執(zhí)行的,而是被一個叫做虛擬機的進(jìn)程托管的。首先必須虛擬機進(jìn)程啟動就緒,然后由虛擬機中的類加載器加載必要的class文件,包括jdk中的基礎(chǔ)類(如String和Object等),然后由虛擬機進(jìn)程解釋class字節(jié)碼指令,把這些字節(jié)碼指令翻譯成本機cpu能夠識別的指令,才能在cpu上運行。
從這個層面上來看,在執(zhí)行一個所謂的java程序的時候,真真正正在執(zhí)行的是一個叫做Java虛擬機的進(jìn)程,而不是我們寫的一個個的class文件。這個叫做虛擬機的進(jìn)程處理一些底層的操作,比如內(nèi)存的分配和釋放等等。我們編寫的class文件只是虛擬機進(jìn)程執(zhí)行時需要的“原料”。這些“原料”在運行時被加載到虛擬機中,被虛擬機解釋執(zhí)行,以控制虛擬機實現(xiàn)我們java代碼中所定義的一些相對高層的操作,比如創(chuàng)建一個文件等,可以將class文件中的信息看做對虛擬機的控制信息,也就是一種虛擬指令。
編程語言也有自己的原理, 學(xué)習(xí)一門語言, 主要是把它的原理搞明白。 看似一個簡單的HelloWorld程序, 也有很多深入的內(nèi)容值得剖析。
JVM體系結(jié)構(gòu)簡介
為了展示虛擬機進(jìn)程和class文件的關(guān)系,特意畫了下面一張圖:
根據(jù)上圖表達(dá)的內(nèi)容,我們編譯之后的class文件是作為Java虛擬機的原料被輸入到Java虛擬機的內(nèi)部的,那么具體由誰來做這一部分工作呢?其實在Java虛擬機內(nèi)部,有一個叫做類加載器的子系統(tǒng),這個子系統(tǒng)用來在運行時根據(jù)需要加載類。注意上面一句話中的“根據(jù)需要”四個字。在Java虛擬機執(zhí)行過程中,只有他需要一個類的時候,才會調(diào)用類加載器來加載這個類,并不會在開始運行時加載所有的類。就像一個人,只有餓的時候才去吃飯,而不是一次把一年的飯都吃到肚子里。一般來說,虛擬機加載類的時機,在第一次使用一個新的類的時候。本專欄后面的文章會具體討論Java中的類加載器。
由虛擬機加載的類,被加載到Java虛擬機內(nèi)存中之后,虛擬機會讀取并執(zhí)行它里面存在的字節(jié)碼指令。虛擬機中執(zhí)行字節(jié)碼指令的部分叫做執(zhí)行引擎。就像一個人,不是把飯吃下去就完事了,還要進(jìn)行消化,執(zhí)行引擎就相當(dāng)于人的腸胃系統(tǒng)。在執(zhí)行的過程中還會把各個class文件動態(tài)的連接起來。關(guān)于執(zhí)行引擎的具體行為和動態(tài)鏈接相關(guān)的內(nèi)容也會在本專欄后續(xù)的文章中進(jìn)行討論。
我們知道,Java虛擬機會進(jìn)行自動內(nèi)存管理。具體說來就是自動釋放沒有用的對象,而不需要程序員編寫代碼來釋放分配的內(nèi)存。這部分工作由垃圾收集子系統(tǒng)負(fù)責(zé)。
從上面的論述可以知道, 一個Java虛擬機實例在運行過程中有三個子系統(tǒng)來保障它的正常運行,分別是類加載器子系統(tǒng), 執(zhí)行引擎子系統(tǒng)和垃圾收集子系統(tǒng)。 如下圖所示:
虛擬機的運行,必須加載class文件,并且執(zhí)行class文件中的字節(jié)碼指令。它做這么多事情,必須需要自己的空間。就像人吃下去的東西首先要放在胃中。虛擬機也需要空間來存放個中數(shù)據(jù)。首先,加載的字節(jié)碼,需要一個單獨的內(nèi)存空間來存放;一個線程的執(zhí)行,也需要內(nèi)存空間來維護方法的調(diào)用關(guān)系,存放方法中的數(shù)據(jù)和中間計算結(jié)果;在執(zhí)行的過程中,無法避免的要創(chuàng)建對象,創(chuàng)建的對象需要一個專門的內(nèi)存空間來存放。關(guān)于虛擬機運行時數(shù)據(jù)區(qū)的內(nèi)容,也會出現(xiàn)在本專欄后續(xù)的文章中。虛擬機的運行時內(nèi)存區(qū)大概可以分成下圖所示的幾個部分。(這里只是大概劃分,并沒有劃分的很精細(xì))
總結(jié)
寫到這里,基本上關(guān)于我對java虛擬機的理解就寫完了。這篇文章的主題雖然是深入理解Java虛擬機,但是你可能感覺一點也不“深入”,也只是泛泛而談。我也有這樣的感覺。限于自己水平有限,也只能這樣了,要是想深入理解java虛擬機,強烈建議讀一下三本書:
《深入Java虛擬機》
《深入理解Java虛擬機JVM高級特性與最佳實踐》
《Java虛擬機規(guī)范》
其實我也讀過這幾本書,但是它們對虛擬機的解釋也是基于一個外部模型,而沒有深入剖析虛擬機內(nèi)部的實現(xiàn)原理。虛擬機是一個大而復(fù)雜的東西,實現(xiàn)虛擬機的人都是大牛級別的,如果不是參與過虛擬機的實現(xiàn),應(yīng)該很少有人能把它參透。本專欄后面的一些文章也參考了這三本書, 雖然講解Java語法的書不計其數(shù), 但是深入講解虛擬機的書, 目前為止我就見過這三本,并且網(wǎng)上的資料也不是很多。
最后做一個總結(jié):
1 虛擬機并不神秘,在操作系統(tǒng)的角度看來,它只是一個普通進(jìn)程。
2 這個叫做虛擬機的進(jìn)程比較特殊,它能夠加載我們編寫的class文件。如果把JVM比作一個人,那么class文件就是我們吃的食物。
3 加載class文件的是一個叫做類加載器的子系統(tǒng)。就好比我們的嘴巴,把食物吃到肚子里。
4 虛擬機中的執(zhí)行引擎用來執(zhí)行class文件中的字節(jié)碼指令。就好比我們的腸胃,對吃進(jìn)去的食物進(jìn)行消化。
5 虛擬機在執(zhí)行過程中,要分配內(nèi)存創(chuàng)建對象。當(dāng)這些對象過時無用了,必須要自動清理這些無用的對象。清理對象回收內(nèi)存的任務(wù)由垃圾收集器負(fù)責(zé)。就好比人吃進(jìn)去的食物,在消化之后,必須把廢物排出體外,騰出空間可以在下次餓的時候吃飯并消化食物。
擴展資料:
關(guān)于JAVA虛擬機的參數(shù)說明如下:
1、運行class文件
執(zhí)行帶main方法的class文件,Java虛擬機[3]?命令參數(shù)行為:
java CLASS文件名
注意:CLASS文件名不要帶文件后綴.class
例如:
java Test
如果執(zhí)行的class文件是帶包的,即在類文件中使用了:
package ;包名
那應(yīng)該在包的基路徑下執(zhí)行,Java虛擬機命令行參數(shù):
java ;包名.CLASS文件名
例如:
PackageTest.java中,其包名為:com.ee2ee.test,對應(yīng)的語句為:
package com.ee2ee.test;
PackageTest.java及編譯后的class文件PackageTest.class的存放目錄如下:
classes
|__com
|__ee2ee
|__test
|__PackageTest.java
|__PackageTest.class
要運行PackageTest.class,應(yīng)在classes目錄下執(zhí)行:
java com.ee2ee.test.PackageTest
2、運行jar文件中的class
原理和運行class文件一樣,只需加上參數(shù)-cp jar文件名;即可。
例如:執(zhí)行test.jar中的類com.ee2ee.test.PackageTest,命令行如下:
java -cp test.jar com.ee2ee.test.PackageTest
3、顯示JDK版本信息
當(dāng)一臺機器上有多個jdk版本時,需要知道當(dāng)前使用的是那個版本的jdk,使用參數(shù)-version即可知道其版本,命令行為:
java -version
4、增加虛擬機可以使用的最大內(nèi)存
Java虛擬機可使用的最大內(nèi)存是有限制的,缺省值通常為64MB或128MB。
如果一個應(yīng)用程序為了提高性能而把數(shù)據(jù)加載內(nèi)存中而占用較大的內(nèi)存,比如超過了默認(rèn)的最大值128MB,需要加大java虛擬機可使用的最大內(nèi)存,否則會出現(xiàn)Out of Memory的異常。啟動java時,需要使用如下兩個參數(shù):
-Xms java虛擬機初始化時使用的內(nèi)存大小
-Xmx java虛擬機可以使用的最大內(nèi)存
以上兩個命令行參數(shù)中設(shè)置的size,可以帶單位,例如:256m表示256MB
舉例說明:
java -Xms128m -Xmx256m ...
表示Java虛擬機初始化時使用的內(nèi)存為128MB,可使用的最大內(nèi)存為256MB。
對于tomcat,可以修改其腳本catalina. sh(Unix平臺)或catalina.bat(Windows平臺),設(shè)置變量JAVA_OPTS即可,例如:
JAVA_OPTS='-Xms128m -Xmx256m'
參考資料:百度百科-java虛擬機
Java:Class.forName()會將一個類加載入到虛擬機嗎?
//會加載類的。
//下面有一個困頌舉例說汪指鄭明。
public class Exam
{
public static void main(String[] args) throws Exception
{
// 會輸出:
// 靜態(tài)初始化塊
// 調(diào)逗仿用函數(shù)設(shè)置靜態(tài)變量N的值為:100
// 所以,Class.forName("C");加載了類C。
Class.forName("C");
}
}
class C
{
static
{
System.out.println("靜態(tài)初始化塊");
}
static int getN()
{
int t=100;
System.out.println("調(diào)用函數(shù)設(shè)置靜態(tài)變量N的值為:"+t);
return t;
}
static int N=C.getN();
}
java虛擬機工作原理?
從宏觀上介紹一下Java虛擬機的工作原理。從最初編寫的Java源文件(.java文件)是如何一步步執(zhí)行的,如下圖所示,首先Java源文件經(jīng)過前端編譯器(javac或ECJ)將.java文件編譯為Java字節(jié)碼文件,然后JRE加載Java字節(jié)碼文件,載入系統(tǒng)分配給JVM的內(nèi)存區(qū),然后執(zhí)行引擎解釋或編譯類文件,再由即時編譯器將字節(jié)碼轉(zhuǎn)化為機器碼。主要介紹下圖中的類加載器和運行時數(shù)據(jù)區(qū)兩個部分。
(1)類加載指將類的字節(jié)碼文件(.class)中的二進(jìn)制數(shù)據(jù)讀入內(nèi)存,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆上創(chuàng)建java.lang.Class對象,封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類加載的最終產(chǎn)品是位于堆中的類對象,類對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向JAVA程序提供了訪問方法區(qū)內(nèi)數(shù)據(jù)結(jié)構(gòu)的接口。如下是類加載器的層次關(guān)系圖。
啟動類加載器(BootstrapClassLoader):在JVM運行時被創(chuàng)建,負(fù)責(zé)加載存放在JDK安裝目錄下的jre\lib的類文件,或者被-Xbootclasspath參數(shù)指定的路徑中,并且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啟動類無法被JAVA程序直接引用。
擴展類加載器(Extension ClassLoader):該類加載器負(fù)責(zé)加載JDK安裝目錄下的\jre\lib\ext的類,或者由java.ext.dirs系統(tǒng)變量指定路徑中的所有類庫,開發(fā)者也可以直接使用擴展類加載器。
應(yīng)用程序類加載器(AppClassLoader):負(fù)責(zé)加載用戶類路徑(Classpath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有定義過自己的類加載器,該類加載器為默認(rèn)的類加載器。
用戶自定義類加載器(User ClassLoader):JVM自帶的類加載器是從本地文件系統(tǒng)加載標(biāo)準(zhǔn)的java class文件,而自定義的類加載器可以做到在執(zhí)行非置信代碼之前,自動驗證數(shù)字簽名,動態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類,從特定的場所(數(shù)據(jù)庫、網(wǎng)絡(luò)中)取得java class。
注意如上的類加載器并不是通過繼承的方式實現(xiàn)的,而是通過組合的方式實現(xiàn)的。而JAVA虛擬機的加載模式是一種委派模式,如上圖中的1-7步所示。下層的加載器能夠看到上層加載器中的類,反之則不行。類加載器可以加載類但是不能卸載類。說了一大堆,還是感覺需要拿點代碼說事。
首先悔陸先定義自己的類加載器MyClassLoader,繼承自ClassLoader,并覆蓋了父類的findClass(String name)方法,如下:
利用定義的類加載器加載指定的字節(jié)碼文件,如通過MyClassLoader加載C:\\Users\\Administrator\\下的Test.class字節(jié)碼文件,代碼如下所示:
(2)運行時數(shù)據(jù)區(qū)
字節(jié)碼的加載第一步,其后分別是認(rèn)證、準(zhǔn)備、解析、初始化,那么這些步驟又具體做了哪些工作,如下圖所示:
(3)如下將介紹運行時數(shù)據(jù)區(qū),主要分為方法區(qū)、碧櫻頃Java堆、虛擬機棧、本地方法棧、程序計數(shù)器。其中方法區(qū)和Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,而頌巖虛擬機棧、本地方法棧、程序計數(shù)器是線程私有的內(nèi)存區(qū)。
Java堆:Java堆是Java虛擬機所管理的內(nèi)存中最大的一塊,被進(jìn)程的所有線程共享,在虛擬機啟動時被創(chuàng)建。該區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存,隨著JIT編譯器的發(fā)展與逃逸分支技術(shù)逐漸成熟,棧上分配、標(biāo)量替換等優(yōu)化技術(shù)使得對象在堆上的分配內(nèi)存變得不是那么“絕對”。Java堆是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在的收集器基本都采用分代收集算法,所以Java堆中還可以分為老年代和新生代(Eden、From Survivor、To Survivor)。根據(jù)Java虛擬機規(guī)范,Java堆可以處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上連續(xù)即可。該區(qū)域的大小可以通過-Xmx和-Xms參數(shù)來擴展,如果堆中沒有內(nèi)存完成實例分配,并且堆也無法擴展,將會拋出OutOfMemoryError異常。
方法區(qū):用于存儲被Java虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。不同于Java堆的是,Java虛擬機規(guī)范對方法區(qū)的限制非常寬松,可以選擇不實現(xiàn)垃圾收集。但并非數(shù)據(jù)進(jìn)入了方法區(qū)就“永久”存在了,這區(qū)域內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載。如果該區(qū)域內(nèi)存不足也會拋出OutOfMemoryError異常。
常量池:這個名詞可能大家也經(jīng)常見,是方法區(qū)的一部分。Class文件除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,用于存放編譯期生成的各種字面量和符號引用。Java虛擬機運行期間,也可能將新的常量放入常量池(如String類的intern()方法)。
虛擬機棧:線程私有,生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行時都會創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。如果請求的站深度大于虛擬機所允許的深度,將拋出StackOverflowError異常,虛擬機棧在動態(tài)擴展時如果無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError異常。
過最簡單的一段代碼解釋一下,程序在運行時數(shù)據(jù)區(qū)個部分的變化情況。
(4)通過編譯器將Test.java文件編譯為Test.class,利用javap -verbose Test.class對編譯后的字節(jié)碼進(jìn)行分析,如下圖所示:
(5)看看運行時數(shù)據(jù)區(qū)的變化:
本文名稱:JAVA虛擬機類加載代碼 java虛擬機加載java文件
文章網(wǎng)址:http://ef60e0e.cn/article/ddpsioj.html