日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

關(guān)于類加載,有兩個非常重要的內(nèi)容,就是類加載器和雙親委派機制,也是面試時常見考核問題。

一、類加載器

還是以這個簡單的代碼為例:

arduino復(fù)制代碼package com.jvm.test; 
public class Book { 
    public static void mAIn(String[] args) { 
        String name = "《三體》"; 
        System.out.printf("一本你不看,都不知道何為“驚艷”二字的書:" + name); 
   } 
}

上面的類的加載是要通過類加載器來實現(xiàn)的。

JAVA中有幾種類加載器:

  • 引導(dǎo)類加載器(BootstrapClassLoader): 這是JVM的內(nèi)置類加載器,負(fù)責(zé)加載支撐JVM運行的位于JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等,即對應(yīng)如java.lang包中的類等。
    它是所有其他類加載器的父加載器,是類加載器層次結(jié)構(gòu)的頂層,由JVM本身實現(xiàn),由C++實現(xiàn)。需要注意的是:這里的“父”,不是我們Java中的類繼承的父類,這里的“父加載器”可以理解是邏輯上的概念,更多是指加載器之間的協(xié)作機制。
  • 擴(kuò)展類加載器(ExtClassLoader)::這個類加載器負(fù)責(zé)加載Java的擴(kuò)展類庫,位于<JAVA_HOME>/lib/ext目錄下的JAR文件。
    他的父加載器是引導(dǎo)類加載器, 通過擴(kuò)展類加載器,可以實現(xiàn)對JVM的擴(kuò)展功能。
  • 應(yīng)用程序類加載器(AppClassLoader):也被稱為系統(tǒng)類加載器,它負(fù)責(zé)加載應(yīng)用程序的類,也就是我們自己編寫的Java代碼的加載器。
    它的父加載器是擴(kuò)展類加載器。
  • 自定義類加載器(CustomClassLoader): 除了上述的三種主要的類加載器,Java還提供自定義類加載器的能力,允許開發(fā)人員根據(jù)需求實現(xiàn)自己的類加載邏輯。
    負(fù)責(zé)加載用戶自定義路徑下的類包。

了解了這幾種不同的加載器,如我們上述實例的代碼的類的加載,應(yīng)該也很容易得知是由 應(yīng)用程序類加載器 來加載。

接下來看一個類加載器的示例:

ini復(fù)制代碼package com.jvm.classloader;

import sun.misc.Launcher;

import java.NET.URL;

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());

        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());

        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

        System.out.println();

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        
        System.out.println("the appClassLoader : " + appClassLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the bootstrapLoader : " + bootstrapLoader);

        System.out.println();
        System.out.println("bootstrapLoader加載以下路徑文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i]);
        }

        System.out.println();
        System.out.println("extClassloader加載以下路徑文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加載以下路徑文件:");
        System.out.println(System.getProperty("java.class.path"));
    }
}

可以思考一下輸出結(jié)果是啥?
輸出結(jié)果:

bash復(fù)制代碼null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
the extClassloader : sun.misc.Launcher$ExtClassLoader@6ce253f1
the bootstrapLoader : null

bootstrapLoader加載以下路徑文件:
file:/Library/Java/JavaVirtualmachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes

extClassloader加載以下路徑文件:
/Users/lan/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

appClassLoader加載以下路徑文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/DNSns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar:/Users/lan/lihy/study/tuling/jvm/jvm-full-gc/target/classes:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.1.2.RELEASE/spring-boot-starter-web-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter/2.1.2.RELEASE/spring-boot-starter-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot/2.1.2.RELEASE/spring-boot-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.2.RELEASE/spring-boot-autoconfigure-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.1.2.RELEASE/spring-boot-starter-logging-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/lan/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/lan/.m2/repository/org/Apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1.jar:/Users/lan/.m2/repository/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1.jar:/Users/lan/.m2/repository/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar:/Users/lan/.m2/repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar:/Users/lan/.m2/repository/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.1.2.RELEASE/spring-boot-starter-json-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.8/jackson-databind-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.8/jackson-core-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.8/jackson-datatype-jdk8-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.8/jackson-datatype-jsr310-2.9.8.jar:/Users/lan/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.8/jackson-module-parameter-names-2.9.8.jar:/Users/lan/.m2/repository/org/springframework/boot/spring-boot-starter-Tomcat/2.1.2.RELEASE/spring-boot-starter-tomcat-2.1.2.RELEASE.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.14/tomcat-embed-core-9.0.14.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.14/tomcat-embed-el-9.0.14.jar:/Users/lan/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.14/tomcat-embed-websocket-9.0.14.jar:/Users/lan/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.14.Final/hibernate-validator-6.0.14.Final.jar:/Users/lan/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/lan/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/lan/.m2/repository/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar:/Users/lan/.m2/repository/org/springframework/spring-web/5.1.4.RELEASE/spring-web-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-beans/5.1.4.RELEASE/spring-beans-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-webmvc/5.1.4.RELEASE/spring-webmvc-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-aop/5.1.4.RELEASE/spring-aop-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-context/5.1.4.RELEASE/spring-context-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-expression/5.1.4.RELEASE/spring-expression-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/lan/.m2/repository/org/springframework/spring-core/5.1.4.RELEASE/spring-core-5.1.4.RELEASE.jar:/Users/lan/.m2/repository/org/springframework/spring-jcl/5.1.4.RELEASE/spring-jcl-5.1.4.RELEASE.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar

Process finished with exit code 0

通過前面對幾種類加載的了解,對這個輸出結(jié)果應(yīng)該問題不大。
但是可能有幾個小疑問:
1、 System.out.println(
String.class.getClassLoader());這個語句,為何輸出時null?
因為System類是Java核心類庫中的類,它是由引導(dǎo)類加載器加載。引導(dǎo)類加載器是JVM的內(nèi)置加載器,由C++實現(xiàn),因此在Java中就輸出為null。

2、System.out.println("the bootstrapLoader : " + bootstrapLoader);為何也輸出為null? 這里extClassloader.getParent()獲取擴(kuò)展類加載器的父加載器,即引導(dǎo)類加載器,其由C++實現(xiàn),因此在Java中就輸出也就為null。

類加載初始化過程

 

如上類運行加載全過程圖,可知在JVM啟動的過程中,會有一系列的初始化操作,包括創(chuàng)建類加載器、加載核心類庫等。在這個初始化的過程,C++(其實是JVM自身調(diào)用,因為JVM底層是C++實現(xiàn),從底層的角度,就是C++代碼調(diào)用Java)調(diào)用Javasun.misc.Launcher類的構(gòu)造方法 Launcher()創(chuàng)建實例。
在Launcher構(gòu)造方法的內(nèi)部,會創(chuàng)建兩個類加載器:

  • sun.misc.Launcher.ExtClassLoader(擴(kuò)展類加載器)
  • sun.misc.Launcher.AppClassLoader(應(yīng)用程序加載器)

Launcher構(gòu)造器核心源碼

 

JVM默認(rèn)使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader的實例加載我們的應(yīng)用程序。

二、什么是雙親委派?

雙親委派是一種類加載的機制。如果一個類加載器接到加載類的請求時,它首先不會自己嘗試加載這個類,而是把這個請求任務(wù)委托給其父加載器去完成,依次遞歸,如果父加載器可以完成類加載任務(wù),就成功返回。只有父加載器無法完成此加載任務(wù)時,才自己去加載。

JVM類加載器的層級結(jié)構(gòu)圖:

 

我們直接先來看一下應(yīng)用程序類加載器(AppClassLoader)加載類的雙親委派機制源碼,AppClassLoader的loadClass方法最終會調(diào)用其父類ClassLoader的loadClass方法, 該方法核心源碼:

 

該方法的大體邏輯為:

  1. 首選,會檢查自己加載器緩存,查看自己是否已經(jīng)加載過目標(biāo)類,如果已經(jīng)加載過,直接返回。
  2. 如果此類沒有被加載過,則判斷一下是否有父加載器;如果有父加載器,則由父加載器(即 調(diào)用c = parent.loadClass(name, false);),如果沒有父加載器則調(diào)用bootstrap類加載器來加載。
  3. 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調(diào)用當(dāng)前的類加載器的findClass方法(即c = findClass(name);)來完成類加載。

雙親委派機制簡單點說就是:先找父親加載,不行再由兒子自己加載。

三、為什么要設(shè)計雙親委派機制?

  • 沙箱安全機制:核心類庫由引導(dǎo)類加載器(Bootstrap ClassLoader)加載,防止惡意類替代核心類。不同類加載器加載的類相互隔離,防止類之間的沖突。例如自己寫的java.lang.String類是不會被加載的。
  • 保證類的唯一性: 雙親委派機制確保在JVM中同一個類只會被加載一次,避免了類的重復(fù)加載,保證了類的唯一性。
  • 保證類的一致性: 類加載器的層次結(jié)構(gòu)保證了類的一致性。當(dāng)一個類加載器需要加載一個類時,它首先會委派給其父加載器去嘗試加載。如果父加載器無法加載該類,子加載器才會嘗試加載。這種委派鏈?zhǔn)讲檎冶WC了類的一致性。

如果你對唯一性 和 一致性有些混淆,那我們可以借助以下的例子進(jìn)行幫助理解:

唯一性: 就像每個人的身份證號碼都是獨一無二的。在類加載機制中,就像每個類在Java中都有唯一的類加載器來加載,保證不同的類擁有不同的加載器,避免了類之間的沖突和混淆。

一致性: 無論在什么情況下使用身份證,一個人的身份證號碼都是不變的。在類加載中,一致性指的是無論通過哪個類加載器加載同一個類,其類定義,在整個應(yīng)用中都是一致性。

運行嘗試加載自己寫的java.lang.String類:

typescript復(fù)制代碼package java.lang;

public class String {
    public static void main(String[] args) {
        System.out.println(">>> Hello String Class >>>");
    }
}

運行結(jié)果:

arduino復(fù)制代碼錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為:
   public static void main(String[] args)
否則 JavaFX 應(yīng)用程序類必須擴(kuò)展javafx.application.Application

Process finished with exit code 1

問題分析:
當(dāng)運行自己定義的java.lang.String 類時,首先會由系統(tǒng)類加載器(應(yīng)用程序類加載器)嘗試加載這個類。由于類加載的雙親委派機制,當(dāng)應(yīng)用程序類加載器在其類加載緩存無法找到j(luò)ava.lang.String 類時,它會委托父加載器(擴(kuò)展類加載器)嘗試加載。同樣,擴(kuò)展類加載器也無法找到,會繼續(xù)委托給引導(dǎo)類加載器。由于引導(dǎo)類加載器負(fù)責(zé)加載 Java 核心類庫,它會在自己的類路徑中找到系統(tǒng)提供的 java.lang.String 類。因此,最終執(zhí)行的是核心類庫中的 java.lang.String 類,該類沒有定義 main 方法,導(dǎo)致執(zhí)行報錯。

這個示例,證實了雙親委派機制上述所說的沙箱安全機制特性,它阻止了開發(fā)人員在核心類庫中創(chuàng)建同名類來替代原有的核心類。這樣的機制確保了核心類庫的穩(wěn)定性和一致性,同時也防止了開發(fā)人員意外地覆蓋核心類的行為。

全盤負(fù)責(zé)委托機制: “全盤負(fù)責(zé)”是指當(dāng)一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入。

四、自定義類加載器

自定義類加載器只需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是loadClass(String, boolean),實現(xiàn)了雙親委派機制,還有一個方法是findClass,默認(rèn)實現(xiàn)是空方法,所以我們自定義類加載器主要是重寫findClass方法。
代碼示例:

java復(fù)制代碼package com.jvm.classloader;

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié)數(shù)組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        public static void main(String args[]) throws Exception {
            // 初始化自定義類加載器,會先初始化父類ClassLoader,
            // 其中會把自定義類加載器的父加載器設(shè)置為應(yīng)用程序類加載器AppClassLoader
            MyClassLoader classLoader = new MyClassLoader("/Users/lan/data/test");
            // 在路徑/Users/lan/data/test下創(chuàng)建test/com/tuling/jvm 幾級目錄,將Book1類的復(fù)制類Book1.class丟入該目錄
            Class clazz = classLoader.loadClass("com.jvm.test.Book1");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("getName", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());

        }
    }
}

注意:如果classpath下有com.jvm.test.Book1的.class,先刪除。
因為自定義加載器的父加載器是程序類加載器(AppClassLoader),基于類加載的雙親委派機制,比如我們示例中的com.jvm.test.Book1,會被委托給程序類加載器加載,如果classpath下存在此Book1.class,輸出結(jié)果將是:sun.misc.Launcher$AppClassLoader。
因此,為了自定義加載器能按預(yù)期從路徑其類加載路徑/Users/lan/data/test下加載Book1,需要先刪除classpath下的Book1.class。

五、如何打破雙親委派

來一個沙箱安全機制示例,嘗試打破雙親委派機制,主要是通過重寫類加載loadClass方法,實現(xiàn)自己的加載邏輯,不委派給雙親加載。然后用自定義類加載器加載我們自己實現(xiàn)的java.lang.String.class。
代碼示例:

java復(fù)制代碼package com.jvm.classloader;

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll(".", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié)數(shù)組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 重寫類加載方法,實現(xiàn)自己的加載邏輯,不委派給雙親加載
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        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) {
                    // 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.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }

                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

        public static void main(String args[]) throws Exception {
            // 初始化自定義類加載器,會先初始化父類ClassLoader,
            // 其中會把自定義類加載器的父加載器設(shè)置為應(yīng)用程序類加載器AppClassLoader
            MyClassLoader classLoader = new MyClassLoader("/Users/lan/data/test");  // 在路徑/Users/lan/data/test下創(chuàng)建java/lang 幾級目錄,將java.lang.String.class丟入該目錄
            // 嘗試用自己改寫類加載機制去加載自己寫的java.lang.String.class
            Class clazz = classLoader.loadClass("java.lang.String");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());

        }
    }
}

輸出結(jié)果:

php復(fù)制代碼java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:28)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:53)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.main(MyClassLoaderTest.java:72)
Exception in thread "main" java.lang.ClassNotFoundException
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:31)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:53)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at com.jvm.classloader.MyClassLoaderTest$MyClassLoader.main(MyClassLoaderTest.java:72)

從輸出結(jié)果,可以看出即使我們自定義的類加載器打破了雙親委派機制,仍然無法成功加載 java.lang.String類。

這是因為盡管自定義類加載器打破了雙親委派機制,但是由于 Java 虛擬機的安全性設(shè)計,它仍然通過檢查類名是否以 "java." 開頭,禁止加載這些類。這種安全性設(shè)計保障了 Java 的穩(wěn)定性和安全性,防止惡意代碼對核心功能造成損害。

安全檢測的核心源碼

 

另外,java.lang.String.class位于jre/lib/rt.jar。

 

解壓此jar包,即可獲取到String.class:

 

寫到最后

今天,介紹了Java類加載器及雙親委派機制,做下小結(jié):

  1. 類加載與加載器分類: 類的加載是通過類加載器來實現(xiàn)的,主要涉及以下幾種加載器:
  • 引導(dǎo)類加載器(BootstrapClassLoader)
  • 擴(kuò)展類加載器(ExtClassLoader)
  • 應(yīng)用程序類加載器(AppClassLoader)
  • 自定義類加載器(CustomClassLoader)
  1. 雙親委派機制的作用: JVM采用雙親委派機制來加載類,具體表現(xiàn)為:在加載類時,會首先自底向上地委派給父加載器,檢查是否已加載過,若未找到,再自頂向下嘗試加載。這種機制的目的:
  • 沙箱安全機制
  • 類的唯一性保證
  • 類的一致性保證
  1. 自定義加載器的能力: JVM允許用戶自定義加載器,通常通過重寫ClassLoader的findClass方法來實現(xiàn)。這樣的自定義加載器可以從指定路徑中加載特定的類。
    然而,需要注意的是,自定義加載器雖然能夠打破雙親委派機制,但它仍然無法加載以java.開頭的核心類庫中的類。
  2. 如果打破雙親委派: 用戶可以通過重寫類加載方法,不委派給雙親加載,來實現(xiàn)打破雙親委派。
    實際應(yīng)用中,例如Tomcat、JDBC等,這些場景需要在共享的類加載環(huán)境中加載不同版本的類,因此采取了自定義的類加載機制,打破了傳統(tǒng)的雙親委派機制。


原文鏈接:
https://juejin.cn/post/7268820544996163639

分享到:
標(biāo)簽:Java
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定