新聞中心
Java字節(jié)代碼的表現(xiàn)形式是字節(jié)數(shù)組(byte[]), 而Java類在JVM中的表現(xiàn)形式是java. lang. Class類的對(duì)象。 一個(gè)Java類從字節(jié)代碼到能夠在JVM中被運(yùn)用, 需要經(jīng)過加載、鏈接和初始化這三個(gè)步驟。

創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)營(yíng)銷推廣、網(wǎng)站重做改版、蓬安網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5技術(shù)、商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為蓬安等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
這三個(gè)步驟中, 對(duì)開發(fā)人員直接可見的是Java類的加載, 通過運(yùn)用Java類加載器(class loader)可以在運(yùn)行時(shí)辰靜態(tài)的加載一個(gè)Java類;而鏈接和初始化則是在運(yùn)用Java類之前會(huì)發(fā)生的舉措。 本文會(huì)詳細(xì)引見Java類的加載、鏈接和初始化的進(jìn)程。
Java類的加載
Java類的加載是由類加載器來完成的。 普通來說, 類加載器分成兩類:?jiǎn)?dòng)類加載器(bootstrap)和用戶自定義的類加載器(user-defined)。 兩者的區(qū)別在于啟動(dòng)類加載器是由JVM的原生代碼實(shí)現(xiàn)的, 而用戶自定義的類加載器都繼承自Java中的java. lang. ClassLoader類。 在用戶自定義類加載器的部分, 普通JVM都會(huì)提供一些根本實(shí)現(xiàn)。 應(yīng)用順序的開發(fā)人員也可以依據(jù)需要編寫自己的類加載器。 JVM中最常運(yùn)用的是零碎類加載器(system), 它用來啟動(dòng)Java應(yīng)用順序的加載。 通過java. lang. ClassLoader的getSystemClassLoader()方法可以獲取到該類加載器對(duì)象。
類加載器需要完成的最終功能是定義一個(gè)Java類, 即把Java字節(jié)代碼轉(zhuǎn)換成JVM中的java. lang. Class類的對(duì)象。 但是類加載的進(jìn)程并不是這么簡(jiǎn)單。 Java類加載器有兩個(gè)比較重要的特征:層次組織構(gòu)造和代理形式。 層次組織構(gòu)造指的是每個(gè)類加載器都有一個(gè)父類加載器, 通過getParent()方法可以獲取到。 類加載器通過這種父親-后代的方式組織在一起, 構(gòu)成樹狀層次構(gòu)造。 代理形式則指的是一個(gè)類加載器既可以自己完成Java類的定義任務(wù), 也可以代理給其它的類加載器來完成。
由于代理形式的存在, 啟動(dòng)一個(gè)類的加載進(jìn)程的類加載器和最終定義這個(gè)類的類加載器能夠并不是一個(gè)。 前者稱為初始類加載器, 然后者稱為定義類加載器。 兩者的關(guān)聯(lián)在于:一個(gè)Java類的定義類加載器是該類所導(dǎo)入的其它Java類的初始類加載器。 比方類A通過import導(dǎo)入了類 B, 那么由類A的定義類加載器負(fù)責(zé)啟動(dòng)類B的加載進(jìn)程。
普通的類加載器在嘗試自己去加載某個(gè)Java類之前, 會(huì)首先代理給其父類加載器。 當(dāng)父類加載器找不到的時(shí)候, 才會(huì)嘗試自己加載。 這個(gè)邏輯是封裝在java. lang. ClassLoader類的loadClass()方法中的。 普通來說, 父類優(yōu)先的戰(zhàn)略就足夠好了。 在某些狀況下, 能夠需要采取相反的戰(zhàn)略, 即先嘗試自己加載, 找不到的時(shí)候再代理給父類加載器。
這種做法在Java的Web容器中比較常見, 也是Servlet規(guī)范推薦的做法。 比方, Apache Tomcat為每個(gè)Web應(yīng)用都提供一個(gè)獨(dú)立的類加載器, 運(yùn)用的就是自己優(yōu)先加載的戰(zhàn)略。 IBM WebSphere Application Server則允許Web應(yīng)用選擇類加載器運(yùn)用的戰(zhàn)略。
類加載器的一個(gè)重要用途是在JVM中為相同名稱的Java類創(chuàng)立隔離空間。 在JVM中, 判斷兩個(gè)類是否相同, 不僅是依據(jù)該類的二進(jìn)制名稱, 還需要依據(jù)兩個(gè)類的定義類加載器。 只有兩者完全一樣, 才認(rèn)為兩個(gè)類的是相同的。 因此, 即便是異樣的Java字節(jié)代碼, 被兩個(gè)不同的類加載器定義之后, 所失掉的Java類也是不同的。 假如試圖在兩個(gè)類的對(duì)象之間停止賦值操作, 會(huì)拋出java. lang. ClassCastException。
這個(gè)特性為異樣名稱的Java類在JVM中共存創(chuàng)造了條件。 在實(shí)際的應(yīng)用中, 能夠會(huì)要求同一名稱的Java類的不同版本在JVM中可以同時(shí)存在。 通過類加載器就可以滿足這種需求。 這種技術(shù)在OSGi中失掉了廣泛的應(yīng)用。
Java類的鏈接
Java類的鏈接指的是將Java類的二進(jìn)制代碼合并到JVM的運(yùn)行狀態(tài)之中的進(jìn)程。 在鏈接之前, 這個(gè)類必需被成功加載。 類的鏈接包括驗(yàn)證、準(zhǔn)備和解析等幾個(gè)步驟。 驗(yàn)證是用來確保Java類的二進(jìn)制表示在構(gòu)造上是完全正確的。 假如驗(yàn)證進(jìn)程出現(xiàn)錯(cuò)誤的話, 會(huì)拋出java. lang. VerifyError錯(cuò)誤。 準(zhǔn)備進(jìn)程則是創(chuàng)立Java類中的靜態(tài)域, 并將這些域的值設(shè)為默許值。 準(zhǔn)備進(jìn)程并不會(huì)執(zhí)行代碼。
在一個(gè)Java類中會(huì)包含對(duì)其它類或接口的形式援用, 包括它的父類、所實(shí)現(xiàn)的接口、方法的形式參數(shù)和前往值的Java類等。 解析的進(jìn)程就是確保這些被援用的類能被正確的找到。 解析的進(jìn)程能夠會(huì)導(dǎo)致其它的Java類被加載。
不同的JVM實(shí)現(xiàn)能夠選擇不同的解析戰(zhàn)略。 一種做法是在鏈接的時(shí)候, 就遞歸的把所有依賴的形式援用都停止解析。 而另外的做規(guī)律能夠是只在一個(gè)形式援用真正需要的時(shí)候才停止解析。 也就是說假如一個(gè)Java類只是被援用了, 但是并沒有被真正用到, 那么這個(gè)類有能夠就不會(huì)被解析。 思索上面的代碼:
- public class LinkTest . . . {
- public static void main(String[] args) . . . {
- ToBeLinked toBeLinked = null;
- System. out. println(Test link. );
- }
- }
類 LinkTest援用了類ToBeLinked, 但是并沒有真正運(yùn)用它, 只是聲明了一個(gè)變量, 并沒有創(chuàng)立該類的實(shí)例或是訪問其中的靜態(tài)域。 在 Oracle的JDK 6中, 假如把編譯好的ToBeLinked的Java字節(jié)代碼刪除之后, 再運(yùn)行LinkTest, 順序不會(huì)拋出錯(cuò)誤。 這是由于ToBeLinked類沒有被真正用到, 而Oracle的JDK 6所采用的鏈接戰(zhàn)略使得ToBeLinked類不會(huì)被加載, 因此也不會(huì)發(fā)現(xiàn)ToBeLinked的Java字節(jié)代碼實(shí)際上是不存在的。 假如把代碼改成ToBeLinked toBeLinked = new ToBeLinked();之后, 再按照相同的方法運(yùn)行, 就會(huì)拋出異常了。 由于這個(gè)時(shí)候ToBeLinked這個(gè)類被真正運(yùn)用到了, 會(huì)需要加載這個(gè)類。
Java類的初始化
當(dāng)一個(gè)Java類第一次被真正運(yùn)用到的時(shí)候, JVM會(huì)停止該類的初始化操作。 初始化進(jìn)程的主要操作是執(zhí)行靜態(tài)代碼塊和初始化靜態(tài)域。 在一個(gè)類被初始化之前, 它的直接父類也需要被初始化。 但是, 一個(gè)接口的初始化, 不會(huì)引起其父接口的初始化。 在初始化的時(shí)候, 會(huì)按照源代碼中從上到下的順序依次執(zhí)行靜態(tài)代碼塊和初始化靜態(tài)域。 思索上面的代碼:
- public class StaticTest . . . {
- public static int X = 10;
- public static void main(String[] args) . . . {
- System. out. println(Y); //輸入60
- }
- static . . . {
- X = 30;
- }
- public static int Y = X * 2;
- }
在上面的代碼中, 在初始化的時(shí)候, 靜態(tài)域的初始化和靜態(tài)代碼塊的執(zhí)行會(huì)從上到下依次執(zhí)行。 因此變量X的值首先初始化成10, 后來又被賦值成30;而變量Y的值則被初始化成60。
Java類和接口的初始化只有在特定的機(jī)遇才會(huì)發(fā)生, 這些機(jī)遇包括:
創(chuàng)立一個(gè)Java類的實(shí)例。 如:MyClass obj = new MyClass()
調(diào)用一個(gè)Java類中的靜態(tài)方法。 如:MyClass. sayHello()
在頂層Java類中執(zhí)行assert語(yǔ)句。
通過Java反射API也能夠形成類和接口的初始化。 需要注意的是, 當(dāng)訪問一個(gè)Java類或接口中的靜態(tài)域的時(shí)候, 只有真正聲明這個(gè)域的類或接口才會(huì)被初始化。 思索上面的代碼:
- class B . . . {
- static int value = 100;
- static . . . {
- System. out. println(Class B is initialized. ); //輸入
- }
- }
- class A extends B . . . {
- static . . . {
- System. out. println(Class A is initialized. ); //不會(huì)輸入
- }
- }
- public class InitTest . . . {
- public static void main(String[] args) . . . {
- }
創(chuàng)立自己的類加載器
在 Java應(yīng)用開發(fā)進(jìn)程中, 能夠會(huì)需要?jiǎng)?chuàng)立應(yīng)用自己的類加載器。 典型的場(chǎng)景包括實(shí)現(xiàn)特定的Java字節(jié)代碼查找方式、對(duì)字節(jié)代碼停止加密/解密以及實(shí)現(xiàn)同名 Java類的隔離等。 創(chuàng)立自己的類加載器并不是一件復(fù)雜的事情, 只需要繼承自java. lang. ClassLoader類并覆寫對(duì)應(yīng)的方法即可。 java. lang. ClassLoader中提供的方法有不少, 上面引見幾個(gè)創(chuàng)立類加載器時(shí)需要思索的:
- defineClass():這個(gè)方法用來完成從Java字節(jié)代碼的字節(jié)數(shù)組到j(luò)ava. lang. Class的轉(zhuǎn)換。 這個(gè)方法是不能被覆寫的, 普通是用原生代碼來實(shí)現(xiàn)的。
- findLoadedClass():這個(gè)方法用來依據(jù)名稱查找已經(jīng)加載過的Java類。 一個(gè)類加載器不會(huì)重復(fù)加載同一名稱的類。
- findClass():這個(gè)方法用來依據(jù)名稱查找并加載Java類。
- loadClass():這個(gè)方法用來依據(jù)名稱加載Java類。
- resolveClass():這個(gè)方法用來鏈接一個(gè)Java類。
這里比較 容易混淆的是findClass()方法和loadClass()方法的作用。 前面提到過, 在Java類的鏈接進(jìn)程中, 會(huì)需要對(duì)Java類停止解析, 而解析能夠會(huì)導(dǎo)致以后Java類所援用的其它Java類被加載。 在這個(gè)時(shí)候, JVM就是通過調(diào)用以后類的定義類加載器的loadClass()方法來加載其它類的。 findClass()方規(guī)律是應(yīng)用創(chuàng)立的類加載器的擴(kuò)展點(diǎn)。 應(yīng)用自己的類加載器應(yīng)該覆寫findClass()方法來添加自定義的類加載邏輯。 loadClass()方法的默許實(shí)現(xiàn)會(huì)負(fù)責(zé)調(diào)用findClass()方法。
前面提到, 類加載器的代理形式默許運(yùn)用的是父類優(yōu)先的戰(zhàn)略。 這個(gè)戰(zhàn)略的實(shí)現(xiàn)是封裝在loadClass()方法中的。 假如希望修改此戰(zhàn)略, 就需要覆寫loadClass()方法。
上面的代碼給出了自定義的類加載的常見實(shí)現(xiàn)形式:
- public class MyClassLoader extends ClassLoader . . . {
- protected Class findClass(String name) throws ClassNotFoundException . . . {
- byte[] b = null; //查找或生成Java類的字節(jié)代碼
- return defineClass(name, b, 0, b. length);
- }
- }
希望通過以上關(guān)于java中類的加載、鏈接和初始化三方面的介紹,能夠給你帶來幫助。
網(wǎng)頁(yè)題目:談java中類的加載、鏈接和初始化
本文地址:http://m.fisionsoft.com.cn/article/ccecsoi.html


咨詢
建站咨詢
