.類載入器及雙親委派機制

vbcjnmkk發表於2024-09-01

類載入器 載入類 備註
啟動類載入器(Bootstrap ClassLoader) JAVA_HOME/jre/lib 無上級,無法直接訪問 由jvm載入
擴充類載入器(Extension ClassLoader) JAVA_HOME/jre/lib/ext 父載入器為 Bootstrap,顯示為 null 。該類由Bootstrap載入
應用類載入器(Application ClassLoader) classpath 父載入器上級為 Extension,該類由Bootstrap載入
自定義類載入器 自定義路徑 父載入器為 Application,該類由Application ClassLoader載入
1.類載入器繼承結構

  1. 類載入器的核心方法
    方法名 說明
    getParent() 返回該類載入器的父類載入器
    findClass(String name) 查詢名字為name的類,返回的結果是java.lang.Class類的例項
    loadClass(String name) 載入名為name的類,返回java.lang.Class類的例項
    defineClass(String name,byte[] b,int off,int len) 根據位元組陣列b中的資料轉化成Java類,返回的結果是java.lang.Class類的例項

  2. Launcher類原始碼解析
    public class Launcher {
    private static URLStreamHandlerFactory factory = new Factory();
    private static Launcher launcher = new Launcher();
    // 啟動類載入器載入路徑
    private static String bootClassPath =
    System.getProperty("sun.boot.class.path");

    public static Launcher getLauncher() {
    return launcher;
    }

    private ClassLoader loader;

    public Launcher() {
    // Create the extension class loader
    ClassLoader extcl;
    try {
    // 獲取擴充套件類載入器
    extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
    throw new InternalError(
    "Could not create extension class loader", e);
    }

     // Now create the class loader to use to launch the application
     try {
         // 獲取應用類載入器
         loader = AppClassLoader.getAppClassLoader(extcl);
     } catch (IOException e) {
         throw new InternalError(
             "Could not create application class loader", e);
     }
    
     // Also set the context class loader for the primordial thread.
     // 設定執行緒上下文類載入器為應用類載入器
     Thread.currentThread().setContextClassLoader(loader);
    

    }

    /*

    • The class loader used for loading installed extensions.
      */
      static class ExtClassLoader extends URLClassLoader {

      private static volatile ExtClassLoader instance = null;

      /**

      • create an ExtClassLoader. The ExtClassLoader is created
      • within a context that limits which files it can read
        /
        public static ExtClassLoader getExtClassLoader() throws IOException
        {
        if (instance == null) {
        synchronized(ExtClassLoader.class) {
        if (instance == null) {
        instance = createExtClassLoader();
        }
        }
        }
        return instance;
        }
        /
        *
      • 獲取載入路徑
        */
        private static File[] getExtDirs() {
        // 擴充套件類載入器載入路徑
        String s = System.getProperty("java.ext.dirs");
        }

    }

    /**

    • The class loader used for loading from java.class.path.

    • runs in a restricted security context.
      */
      static class AppClassLoader extends URLClassLoader {

      public static ClassLoader getAppClassLoader(final ClassLoader extcl)
      throws IOException
      {
      // 應用類載入器載入路徑
      final String s = System.getProperty("java.class.path");
      final File[] path = (s == null) ? new File[0] : getClassPath(s);
      return AccessController.doPrivileged(
      new PrivilegedAction() {
      public AppClassLoader run() {
      URL[] urls =
      (s == null) ? new URL[0] : pathToURLs(path);
      return new AppClassLoader(urls, extcl);
      }
      });
      }
      }

  3. ClassLoader類原始碼解析
    public abstract class ClassLoader {

    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) {
    long t0 = System.nanoTime();
    try {
    // 委託父載入器載入
    if (parent != null) {
    c = parent.loadClass(name, false);
    } else {
    c = findBootstrapClassOrNull(name);
    }
    } catch (ClassNotFoundException e) {
    // ClassNotFoundException thrown if class not found
    // from the non-null parent class loader
    }

             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.getParentDelegationTime().addTime(t1 - t0);
                 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                 sun.misc.PerfCounter.getFindClasses().increment();
             }
         }
         if (resolve) {
             resolveClass(c);
         }
         return c;
     }
    

    }

    // 自定義類載入器需要重寫該方法
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
    }

}

  1. 雙親委派機制優缺點
    優點:

1、保證安全性,層級關係代表優先順序,也就是所有類的載入,優先給啟動類載入器,這樣就保證了核心類庫類

2、避免類的重複載入,如果父類載入器載入過了,子類載入器就沒有必要再去載入了,確保一個類的全域性唯一性

缺點:

檢查類是否載入的委派過程是單向的, 這個方式雖然從結構上說比較清晰,使各個 ClassLoader 的職責非常明確, 但是同時會帶來一個問題, 即頂層的ClassLoader 無法訪問底層的ClassLoader 所載入的類

通常情況下, 啟動類載入器中的類為系統核心類, 包括一些重要的系統介面,而在應用類載入器中, 為應用類。 按照這種模式, 應用類訪問系統類自然是沒有問題, 但是系統類訪問應用類就會出現問題。

二.spi介面及執行緒上下文類載入器
1.spi介面定義及執行緒上下文載入的作用
Java提供了很多核心介面的定義,這些介面被稱為SPI介面。(Service Provider Interface,SPI),允許第三方為這些介面提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

這些 SPI 的介面由 Java 核心庫來提供,而這些 SPI 的實現程式碼則是作為 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)裡。SPI介面中的程式碼經常需要載入具體的實現類。那麼問題來了,SPI的介面是Java核心庫的一部分,是由啟動類載入器(Bootstrap Classloader)來載入的;SPI的實現類是由系統類載入器(System ClassLoader)來載入的。引導類載入器是無法找到 SPI 的實現類的,因為依照雙親委派模型,BootstrapClassloader無法委派AppClassLoader來載入類。而執行緒上下文類載入器破壞了“雙親委派模型”,可以在執行執行緒中拋棄雙親委派載入鏈模式,使程式可以逆向使用類載入器。

類載入傳導規則:JVM 會選擇當前類的類載入器來載入所有該類的引用的類。例如我們定義了 TestA 和 TestB 兩個類,TestA 會引用 TestB,只要我們使用自定義的類載入器載入 TestA,那麼在執行時,當 TestA 呼叫到 TestB 的時候,

TestB 也會被 JVM 使用 TestA 的類載入器載入。依此類推,只要是 TestA 及其引用類關聯的所有 jar 包的類都會被自定義類載入器載入。透過這種方式,我們只要讓模組的 main 方法類使用不同的類載入器載入,那麼每個模組的都會使用 main
方法類的類載入器載入的,這樣就能讓多個模組分別使用不同類載入器。這也是 OSGi 和 SofaArk 能夠實現類隔離的核心原理。

  1. spi載入原理
    當第三方實現者提供了服務介面的一種實現之後,在jar包的 META-INF/services/ 目錄裡同時建立一個以服務介面命名的檔案,該檔案就是實現該服務介面的實現類。而當外部程式裝配這個模組的時候,就能透過該jar包 META-INF/services/ 裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。

JDK官方提供了一個查詢服務實現者的工具類:java.util.ServiceLoader

public final class ServiceLoader
implements Iterable
{

 // 載入spi介面實現類配置檔案固定路徑
private static final String PREFIX = "META-INF/services/";

/**
* Creates a new service loader for the given service type, using the
* current thread's {@linkplain java.lang.Thread#getContextClassLoader
* context class loader}.
*
*

An invocation of this convenience method of the form
*
*


* ServiceLoader.load(service)

*
* is equivalent to
*
*

* ServiceLoader.load(service,
* Thread.currentThread().getContextClassLoader())

*
* @param the class of the service type
*
* @param service
* The interface or abstract class representing the service
*
* @return A new service loader
*/
public static ServiceLoader load(Class service) {
// 執行緒上下文類載入器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}

3.示列程式碼
程式碼:

public interface IShout {
void shout();
}

public class Dog implements IShout {
@Override
public void shout() {
System.out.println("wang wang");
}
}
public class Cat implements IShout {
@Override
public void shout() {
System.out.println("miao miao");
}
}

public class Main {
public static void main(String[] args) {
ServiceLoader shouts = ServiceLoader.load(IShout.class);
for (IShout s : shouts) {
s.shout();
}
}
}

配置:

4.MySQL驅動類載入

// 載入Class到AppClassLoader(系統類載入器),然後註冊驅動類
//Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
// 透過java庫獲取資料庫連線
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");

public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
。。。。。。。
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

}

三.自定義動態類載入器
1.示例程式碼
public class DynamicClassLoad extends ClassLoader{

public static void main(String[] args) {

    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            try {
                DynamicClassLoad myClassLoad = new DynamicClassLoad();
                Class clazz = myClassLoad.findClass("/Users/wangzhaoqing1/Desktop/MyTest.class");
                Object obj = clazz.newInstance();
                Method sayHello = clazz.getDeclaredMethod("sayHello");
                sayHello.invoke(obj, null);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }, 1, 2, TimeUnit.SECONDS);
}


@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    File file = new File(name);
    try {
        byte[] bytes = FileUtils.readFileToByteArray(file);
        Class<?> c = this.defineClass(null, bytes, 0, bytes.length);
        return c;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return super.findClass(name);
}

}

// DynamicClassLoad啟動後,修改本類重新編譯
//程式碼效果參考:http://www.ningluan.com/sitemap/post.xml
public class MyTest {

public void  sayHello(){
    System.out.println("hello wzq 6666666666");
}

}

相關文章