1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java 類加載器解析及常見類加載問題

        共 13338字,需瀏覽 27分鐘

         ·

        2022-06-12 21:35

        原文 https://www.toutiao.com/article/6812564562244534787

        java.lang.ClassLoader

        每個(gè)類加載器本身也是個(gè)對象——一個(gè)繼承 java.lang.ClassLoader 的實(shí)例。每個(gè)類被其中一個(gè)實(shí)例加載。我們下面來看看 java.lang.ClassLoader 中的 API, 不太相關(guān)的部分已忽略。

        package java.lang;

        public abstract class ClassLoader {

          public Class loadClass(String name);
          protected Class defineClass(byte[] b);

          public URL getResource(String name);
          public Enumeration getResources(String name);
          
          public ClassLoader getParent()
        }

        loadClass: 目前 java.lang.ClassLoader 中最重要的方法是 loadClass 方法,它獲取要加載的類的全限定名返回 Class 對象。

        defineClass: defineClass 方法用于具體化 JVM 的類。byte 數(shù)組參數(shù)是加載自磁盤或其他位置的類字節(jié)碼。

        getResource 和 getResources: 返回資源路徑。loadClass 大致相當(dāng)于 defineClass(getResource(name).getBytes())。

        getParent: 返回父加載器。

        Java 的懶惰特性影響了類加載器的工作方式——所有事情都應(yīng)該在最后一刻完成。類只有在以某種方式被引用時(shí)才會被加載-通過調(diào)用構(gòu)造函數(shù)、靜態(tài)方法或字段??磦€(gè)例子:

        類 A 實(shí)例化類 B:

        public class A {
           public void doSomething() {
            B b = new B();
             b.doSomethingElse();
           }
         }

        語句 B b = new B() 在語義上等同于 B b = A.class. getClassLoader().loadClass(“B”).newInstance() 。如我們所見,Java 中的每個(gè)對象都與其類 (A.class) 相關(guān)聯(lián),并且每個(gè)類都與用于加載類的類加載器 (A.class.getClassLoader()) 相關(guān)聯(lián)。

        當(dāng)我們實(shí)例化類加載器時(shí),我們可以將父類加載器指定為構(gòu)造函數(shù)參數(shù)。如果未顯式指定父類加載器,則會將虛擬機(jī)的系統(tǒng)類加載器指定為默認(rèn)父類。

        類加載器層次結(jié)構(gòu)

        每當(dāng)啟動新的 JVM 時(shí),引導(dǎo)類加載器(bootstrap classloader)負(fù)責(zé)首先將關(guān)鍵 Java 類(來自 Java.lang 包)和其他運(yùn)行時(shí)類加載到內(nèi)存中。引導(dǎo)類加載器是所有其他類加載器的父類。因此,它是唯一沒有父類的。

        接下來是擴(kuò)展類加載器(extension classloader)。引導(dǎo)類加載器(bootstrap classloader)作為父類,負(fù)責(zé)從 java.ext.dirs 路徑中保存的所有 .jar 文件加載類。

        從開發(fā)人員的角度來看,第三個(gè)也是最重要的類加載器是系統(tǒng)類路徑類加載器(system classpath classloader),它是擴(kuò)展類加載器(extension classloader)的直接子類。它從由 CLASSPATH 環(huán)境變量 java.class.pat h系統(tǒng)屬性或 -classpath 命令行選項(xiàng)指定的目錄和 jar 文件加載類。


        請注意,類加載器層次結(jié)構(gòu)不是繼承層次結(jié)構(gòu),而是委托層次結(jié)構(gòu)。大多數(shù)類加載器在搜索自己的類路徑之前將查找類和資源委托給其父類。如果父類加載器找不到類或資源,則類加載器只能嘗試在本地找到它們。實(shí)際上,類加載器只負(fù)責(zé)加載父級不可用的類;層次結(jié)構(gòu)中較高的類加載器加載的類不能引用層次結(jié)構(gòu)中較低的可用類。類加載器委托行為的動機(jī)是避免多次加載同一個(gè)類。

        在 Java EE 中,查找的順序通常是相反的:類加載器可能在轉(zhuǎn)到父類之前嘗試在本地查找類。

        Java EE 委托模型

        下面是應(yīng)用程序容器的類加載器層次結(jié)構(gòu)的典型視圖:容器本身有一個(gè)類加載器,每個(gè) EAR 模塊都有自己的類加載器,每個(gè) WAR 都有自己的類加載器。Java Servlet 規(guī)范建議 web 模塊的類加載器在委托給其父類之前先在本地類加載器中查找——父類加載器只要求提供模塊中找不到的資源和類。

        在某些應(yīng)用程序容器中,遵循此建議,但在其他應(yīng)用程序容器中,web 模塊的類加載器配置為遵循與其他類加載器相同的委托模型,因此建議參考您使用的應(yīng)用程序容器的文檔。

        顛倒本地查找和委托查找之間的順序的原因是,應(yīng)用程序容器附帶了許多具有自己的發(fā)布周期的庫,這些庫可能不適用于應(yīng)用程序開發(fā)人員。典型的例子是 log4j 庫——它的一個(gè)版本通常隨容器一起提供,不同的版本與應(yīng)用程序捆綁在一起。

        現(xiàn)在,讓我們來看看我們可能遇到的幾個(gè)常見的類加載問題,并提供可能的解決方案。

        常見類加載問題

        Java EE 委托模型會導(dǎo)致類加載的一些有趣的問題。NoClassDefFoundError、LinkageError、ClassNotFoundException、NoSuchMethodError、ClassCasteException等是開發(fā) Java EE 應(yīng)用程序時(shí)遇到的非常常見的異常。我們可以對這些問題的根本原因做出各種假設(shè),但重要的是要驗(yàn)證它們。

        NoClassDefFoundError

        NoClassDefFoundError 是開發(fā) Java EE Java 應(yīng)用程序時(shí)最常見的問題之一。

        根本原因分析和解決過程的復(fù)雜性主要取決于 Java EE 中間件環(huán)境的大??;特別是考慮到各種 Java EE 應(yīng)用程序中存在大量的類加載器。

        正如 Javadoc 條目所說,如果 Java 虛擬機(jī)或類加載器實(shí)例試圖在類的定義中加載,而找不到類的定義,則拋出 NoClassDefFoundError。這意味著,在編譯當(dāng)前執(zhí)行的類時(shí),搜索到的類定義存在,但在運(yùn)行時(shí)找不到該定義。

        這就是為什么你不能總是依賴你的 IDE 告訴你一切正常,代碼編譯應(yīng)該正常工作。相反,這是一個(gè)運(yùn)行時(shí)問題,IDE 在這里無法提供幫助。

        讓我們看看下面的例子:

        public class HelloServlet extends HttpServlet {
           protected void doGet(HttpServletRequest request,
                                            HttpServletResponse response)
                                            throws ServletException, IOException {
               PrintWriter out = response.getWriter();
               out.print(new Util().sayHello());
        }

        servlet HelloServlet 實(shí)例化了 Util 類的一個(gè)實(shí)例,該實(shí)例提供了要打印的消息。遺憾的是,當(dāng)請求執(zhí)行時(shí),我們可能會看到以下內(nèi)容:

        java.lang.NoClassdefFoundError: Util
         HelloServlet:doGet(HelloServlet.java:17)
         javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
         javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        我們?nèi)绾谓鉀Q這個(gè)問題?好吧,您可能要做的最明顯的操作是檢查丟失的 Util 類是否已實(shí)際包含在包中。

        我們在這里可以使用的技巧之一是讓容器類加載器承認(rèn)它從何處加載資源。為此,我們可以嘗試將 HelloServlet 的類加載器轉(zhuǎn)換為 URLClassLoader 并請求其類路徑。

        public class HelloServlet extends HttpServlet {
           protected void doGet(HttpServletRequest request,
                                            HttpServletResponse response)
                                            throws ServletException, IOException {
               PrintWriter out = response.getWriter();
               out.print(Arrays.toString(
                   ((URLClassLoader)HelloServlet.class.getClassLoader()).getURLs()));
        }

        結(jié)果很可能是這樣:

        file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/classes,
        file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/lib/demo-lib.jar

        資源的路徑( file:/Users/myuser/eclipse/workspace/.metadata/)實(shí)際上顯示容器是從 Eclipse 啟動的,這是 IDE 解壓歸檔文件來進(jìn)行部署的地方?,F(xiàn)在我們可以檢查丟失的 Util 是否真的包含在 demo-lib.jar 中,或者它是否存在于擴(kuò)展存檔的 WEB-INF/classes 目錄中。

        因此,對于我們的特定示例,可能是這樣的情況:Util 類應(yīng)該打包到 demo-lib.jar 中,但是我們沒有重新啟動構(gòu)建過程,并且該類沒有包含在以前存在的包中,因此出現(xiàn)了錯(cuò)誤。

        URLClassLoader 技巧可能不適用于所有應(yīng)用服務(wù)器。另一種方法是使用jconsole 實(shí)用程序附加到容器JVM進(jìn)程,以檢查類路徑。例如,屏幕截圖(如下)演示了連接到 JBoss application server 進(jìn)程的 jconsole 窗口,我們可以從運(yùn)行時(shí)屬性中看到 ClassPath 屬性值。


        NoSuchMethodError

        在另一個(gè)具有相同示例的場景中,我們可能會遇到以下異常:

        java.lang.NoSuchMethodError: Util.sayHello()Ljava/lang/String;
         HelloServlet:doGet(HelloServlet.java:17)
         javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
         javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        NoSuchMethodError 代表另一個(gè)問題。在本例中,我們所引用的類存在,但加載的類版本不正確,因此找不到所需的方法。

        要解決這個(gè)問題,我們首先必須了解類是從何處加載的。最簡單的方法是向 JVM 添加 '-verbose:class' 命令行參數(shù),但是如果您可以快速更改代碼,那么您可以使用 getResource 搜索與 loadClass 相同的類路徑。

        public class HelloServlet extends HttpServlet {
           protected void doGet(HttpServletRequest request,
                                            HttpServletResponse response)
                                            throws ServletException, IOException {
               PrintWriter out = response.getWriter();
            out.print(HelloServlet.class.getClassLoader().getResource(
                   Util.class.getName.replace(‘.’, ‘/’) + “.class”));  

        }

        假設(shè),上述示例的請求執(zhí)行結(jié)果如下.

        file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/lib/demo-lib.jar!/Util.class

        現(xiàn)在我們需要驗(yàn)證關(guān)于類的錯(cuò)誤版本的假設(shè)。我們可以使用javap實(shí)用程序來反編譯類,然后我們可以看到所需的方法是否實(shí)際存在。

        $ javap -private Util
        Compiled from “Util.java”
        public class Util extends java.lang.Object {
           public Util();
        }

        如您所見,Util 類的反編譯版本中沒有sayHello方法??赡?,我們在 demo-lib.jar 中打包了 Util 類的初始版本,但是在添加了新的 sayHello 方法之后,我們沒有重新構(gòu)建這個(gè)包。

        在處理 Java EE 應(yīng)用程序時(shí),錯(cuò)誤類問題 NoClassDefFoundError 和 NoSuchMethodError 的變體是非常典型的,這是 Java 開發(fā)人員理解這些錯(cuò)誤的本質(zhì)以有效解決問題所必需的技能。

        這些問題有很多變體:AbstractMethodError、ClassCastException、IllegalAccessError——基本上,當(dāng)我們認(rèn)為應(yīng)用程序使用類的一個(gè)版本,但實(shí)際上它使用了其他版本,或者類的加載方式與需要的不同時(shí),這些問題都會遇到。

        ClassCastException

        這里我們只演示 ClassCastException 例子。我們將以使用工廠修改初始示例,以便提供提供問候消息的類的實(shí)現(xiàn)。這看起來很做作,但這是很常見的模式。

        public class HelloServlet extends HttpServlet {
           protected void doGet(HttpServletRequest request, 
                                            HttpServletResponse response) 
                                            throws ServletException, IOException {
               PrintWriter out = response.getWriter();
            out.print(((Util)Factory.getUtil()).sayHello());
        }

        class Factory {
             public static Object getUtil() {
                  return new Util();
             }
        }

        請求的可能結(jié)果是:

        java.lang.ClassCastException: Util cannot be cast to Util
            HelloServlet:doGet(HelloServlet.java:18)
            javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
            javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        這意味著 HelloServlet 和 Factory 類在不同的上下文中操作。我們必須弄清楚這些類是如何加載的。讓我們使用 -verbose:class 并找出如何加載與HelloServlet 和 Factory 類相關(guān)的 Util 類。

        [Loaded Util from file:/Users/ekabanov/Applications/ apache-tomcat-6.0.20/lib/cl-shared-jar.jar] 
        [Loaded Util from file:/Users/ekabanov/Documents/workspace-javazone/.metadata/.plugins/org.eclipse.wst. server.core/tmp0/wtpwebapps/cl-demo/WEB-INF/lib/cl-demo- jar.jar]

        因此,Util類由不同的類加載器從兩個(gè)不同的位置加載。一個(gè)在web應(yīng)用程序類加載器中,另一個(gè)在應(yīng)用程序容器類加載器中。它們是不兼容的,不能相互轉(zhuǎn)換。


        但它們?yōu)槭裁床幌嗳菽兀吭瓉鞪ava中的每個(gè)類都是由其完全限定名唯一標(biāo)識的。但在1997年發(fā)表的一篇論文揭露了由此引起的一個(gè)廣泛的安全問題,即沙盒應(yīng)用程序(例如:applet)可以定義任何類,包括 java.lang.String,并在沙盒外注入自己的代碼。

        解決方案是通過完全限定名和類加載器的組合來標(biāo)識類!這意味著從類加載器 A 加載的 Util 類和從類加載器 B 加載的 Util 類在 JVM 中是不同的類,不能將一個(gè)類轉(zhuǎn)換為另一個(gè)類!

        這個(gè)問題的根源是 web 類加載器的反向行為。如果 web 類加載器的行為與其他類加載器相同,那么 Util 類將從應(yīng)用程序容器類加載器加載一次,并且不會拋出類 CastException。

        LinkageError

        讓我們從前面的示例中稍微修改一下 Factory 類,這樣 getUtil 方法現(xiàn)在返回的是 Util 類型而不是 Object:

        class Factory {
             public static Util getUtil() {
                  return new Util();
             }
        }

        現(xiàn)在,執(zhí)行的結(jié)果是 LinkageError:

        ClassCastException: java.lang.LinkageError: loader constraint violation: when resolving method Factory.getUtil()LUtil;
        <…> HelloServlet:doGet(HelloServlet.java:18)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:617) javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        根本問題與 ClassCastException 相同——唯一的區(qū)別是我們不強(qiáng)制轉(zhuǎn)換對象,而是加載程序約束導(dǎo)致Linkage錯(cuò)誤。

        在處理類加載器時(shí),一個(gè)非常重要的原則是認(rèn)識到類加載器的行為常常會破壞您的直觀理解,因此驗(yàn)證您的假設(shè)非常重要。例如,在 LinkageError 的情況下,查看代碼或構(gòu)建過程將阻礙而不是幫助您。關(guān)鍵是查看類的確切加載位置,它們是如何到達(dá)那里的,以及如何防止將來發(fā)生這種情況。

        多個(gè)類加載器中存在相同類的一個(gè)常見原因是,同一個(gè)庫的不同版本捆綁在不同的位置,例如應(yīng)用服務(wù)器和 web 應(yīng)用程序。這通常發(fā)生在像 log4j 或 hibernate 這樣的實(shí)際標(biāo)準(zhǔn)庫中。在這種情況下,解決方案要么是將庫與 web 應(yīng)用程序分開,要么是非常小心地避免使用父類加載器中的類。

        IllegalAccessError

        其實(shí),不僅類由其全限定名和類加載器標(biāo)識,而且該規(guī)則也適用于包。為了演示這一點(diǎn),我們將 Factory.getUtil 方法的訪問修飾符更改為默認(rèn)值:

        class Factory {
             static Object getUtil() {
                  return new Util();
             }
        }

        假設(shè) HelloServlet 和 Factory 都位于同一個(gè)(默認(rèn))包中,因此 getUtil 在 HelloServlet 類中可見。不幸的是,如果我們試圖在運(yùn)行時(shí)訪問它,我們將看到 IllegalAccessError 異常。

        java.lang.IllegalAccessError: tried to access method Factory.getUtil()Ljava/lang/Object;
        HelloServlet:doGet(HelloServlet.java:18)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

        盡管訪問修飾符對于應(yīng)用程序的編譯是正確的,但是在運(yùn)行時(shí),這些類是從不同的類加載器加載的,應(yīng)用程序無法運(yùn)行。這是由于與類一樣,包也由它們的完全限定名和類加載器來標(biāo)識,出于同樣的安全原因。

        ClassCastException、LinkageError 和 IllegalAccessError 根據(jù)實(shí)現(xiàn)有點(diǎn)不同,但根本原因是相同的類被不同的類加載器加載。

        Java 類加載器備忘單


        No class found

        Variants

        • ClassNotFoundException
        • NoClassDefFoundError

        Helpful

        • IDE class lookup (Ctrl+Shift+T in Eclipse)
        • find *.jar -exec jar -tf '{}'; | grep MyClass
        • URLClassLoader.getUrls() Container specific logs

        Wrong class found

        Variants

        • IncompatibleClassChangeError AbstractMethodError NoSuch(Method|Field)Error
        • ClassCastException, IllegalAccessError

        Helpful

        • -verbose:class
        • ClassLoader.getResource() javap -private MyClass

        More than one class found

        • LinkageError (class loading constraints violated)
        • ClassCastException, IllegalAccessError

        Helpful

        • -verbose:class
        • ClassLoader.getResource()
        我們創(chuàng)建了一個(gè)高質(zhì)量的技術(shù)交流群,與優(yōu)秀的人在一起,自己也會優(yōu)秀起來,趕緊點(diǎn)擊加群,享受一起成長的快樂。另外,如果你最近想跳槽的話,年前我花了2周時(shí)間收集了一波大廠面經(jīng),節(jié)后準(zhǔn)備跳槽的可以點(diǎn)擊這里領(lǐng)取!

        推薦閱讀

        ··································

        你好,我是程序猿DD,10年開發(fā)老司機(jī)、阿里云MVP、騰訊云TVP、出過書創(chuàng)過業(yè)、國企4年互聯(lián)網(wǎng)6年。從普通開發(fā)到架構(gòu)師、再到合伙人。一路過來,給我最深的感受就是一定要不斷學(xué)習(xí)并關(guān)注前沿。只要你能堅(jiān)持下來,多思考、少抱怨、勤動手,就很容易實(shí)現(xiàn)彎道超車!所以,不要問我現(xiàn)在干什么是否來得及。如果你看好一個(gè)事情,一定是堅(jiān)持了才能看到希望,而不是看到希望才去堅(jiān)持。相信我,只要堅(jiān)持下來,你一定比現(xiàn)在更好!如果你還沒什么方向,可以先關(guān)注我,這里會經(jīng)常分享一些前沿資訊,幫你積累彎道超車的資本。

        點(diǎn)擊領(lǐng)取2022最新10000T學(xué)習(xí)資料
        瀏覽 40
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美色综合天天久久综合精品 | 九九精品在线视频 | AV天堂偷拍亂伦 | 狂野3p欧美激情性xxxx | 欧美 日韩 人妻 高清 中文 | av作品番号库大全 | 91国精产品成品人人入口 | 中文字幕人妻丝袜 | 色色热视频 | 成人高清免费观看MV |