Java類加載機制及自定義加載器


  一:ClassLoader類加載器,主要的作用是將class文件加載到jvm虛擬機中。jvm啟動的時候,並不是一次性加載所有的類,而是根據需要動態去加載類,主要分為隱式加載和顯示加載。

  隱式加載:程序代碼中不通過調用ClassLoader來加載需要的類,而是通過JVM類自動加載需要的類到內存中。例如,當我們在類中繼承或者引用某個類的時候,JVM在解析當前這個類的時,發現引用的類不在內存中,那么就會自動將這些類加載到內存中。

  顯示加載:代碼中通過Class.forName(),this.getClass.getClassLoader.LoadClass(),自定義類加載器中的findClass()方法等。

  二:jvm自帶的加載器

  (1)BootStrap ClassLoader:主要加載%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。可以通System.getProperty("sun.boot.class.path")

     查看加載的路徑,如下:

package test;

public class TestGC {
    
    public static void main(String []args){

        System.out.println(System.getProperty("sun.boot.class.path"));
        
    }
}

    顯示結果如下:

D:\Program Files\Java\jdk1.7.0_45\jre\lib\resources.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\rt.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\sunrsasign.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jce.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.7.0_45\jre\classes

  (2)Extention ClassLoader:主要加載目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。也可以通過System.out.println(System.getProperty("java.ext.dirs"))查看加載類文件的路徑。

  (3)AppClassLoader:主要加載當前應用下的classpath路徑下的類。之前我們在環境變量中配置的classpath就是指定AppClassLoader的類加載路徑。

  三:類加載器的繼承關系

  先看一下這三個類加載器之間的繼承關系,如下圖:

  

  ExtClassLoader,AppClassLoder繼承URLClassLoader,而URLClassLoader繼承ClassLoader,BoopStrap ClassLoder不在上圖中,因為它是由C/C++編寫的,它本身是虛擬機的一部分,並不是一個java類。jvm加載的順序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder,下面看一段源碼:

public class Launcher {
    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);
        }

        Thread.currentThread().setContextClassLoader(loader);
    }

    /*
     * Returns the class loader used to launch the main application.
     */
    public ClassLoader getClassLoader() {
        return loader;
    }
    /*
     * The class loader used for loading installed extensions.
     */
    static class ExtClassLoader extends URLClassLoader {}

/**
     * The class loader used for loading from java.class.path.
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {}

   從源碼中我們看到:(1)Launcher初始化的時候創建了ExtClassLoader以及AppClassLoader,並將ExtClassLoader實例傳入到AppClassLoader中。

   (2)雖然上一段源碼中沒見到創建BoopStrap ClassLoader,但是程序一開始就執行了System.getProperty("sun.boot.class.path")。

  四:類加載器之間的父子關系

    AppClassLoader的父加載器為ExtClassLoader,ExtClassLoader的父加載器為null,BoopStrap ClassLoader為頂級加載器。

  下面一個小例子就可以證明,如下:新建一個Test類,可以通過getParent()方法獲取上一層父機載器,執行如下代碼:

package test;

public class TestGC {
    
    public static void main(String []args){
        
        System.out.println(Test.class.getClassLoader().toString());
        
        System.out.println(Test.class.getClassLoader().getParent().toString());
        
        System.out.println(Test.class.getClassLoader().getParent().getParent().toString());
    }
}

  輸出結果如下:

  

  五:類加載機制-雙親委托機制

  例如:當jvm要加載Test.class的時候,

  (1)首先會到自定義加載器中查找,看是否已經加載過,如果已經加載過,則返回字節碼。

  (2)如果自定義加載器沒有加載過,則詢問上一層加載器(即AppClassLoader)是否已經加載過Test.class。

  (3)如果沒有加載過,則詢問上一層加載器(ExtClassLoader)是否已經加載過。

  (4)如果沒有加載過,則繼續詢問上一層加載(BoopStrap ClassLoader)是否已經加載過。

  (5)如果BoopStrap ClassLoader依然沒有加載過,則到自己指定類加載路徑下("sun.boot.class.path")查看是否有Test.class字節碼,有則返回,沒有通

知下一層加載器ExtClassLoader到自己指定的類加載路徑下(java.ext.dirs)查看。

  (6)依次類推,最后到自定義類加載器指定的路徑還沒有找到Test.class字節碼,則拋出異常ClassNotFoundException。如下圖:

  

  六:類加載過程的幾個方法

  (1)loadClass     (2)findLoadedClass     (3)findClass     

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,檢查是否已經加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加載器不為空,調用父加載器的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        //父加載器為空則,調用Bootstrap Classloader
                        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();
                    //父加載器沒有找到,則調用findclass
                    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()
                resolveClass(c);
            }
            return c;
        }
    }

  七:自定義類加載器步驟

  (1)繼承ClassLoader    (2)重寫findClass()方法   (3)調用defineClass()方法

  下面寫一個自定義類加載器:指定類加載路徑在D盤下的lib文件夾下。

  (1)新建一個Test.class類,代碼如下:

package com.test;

public class Test {

    public void say(){
System.out.println(
"Hello MyClassLoader"); } }

  (2)cmd控制台執行javac Test.java,將生成的Test.class文件放到D盤lib文件夾->com文件夾->test文件夾下。

  (3)自定義類加載器,代碼如下:

package test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{

    private String classpath;
    
    public MyClassLoader(String classpath) {
        
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte [] classDate=getDate(name);
            
            if(classDate==null){}
            
            else{
                //defineClass方法將字節碼轉化為類
                return defineClass(name,classDate,0,classDate.length);
            }
            
        } catch (IOException e) {
            
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    //返回類的字節碼
    private byte[] getDate(String className) throws IOException{
        InputStream in = null;
        ByteArrayOutputStream out = null;
        String path=classpath + File.separatorChar +
                    className.replace('.',File.separatorChar)+".class";
        try {
            in=new FileInputStream(path);
            out=new ByteArrayOutputStream();
            byte[] buffer=new byte[2048];
            int len=0;
            while((len=in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            return out.toByteArray();
        } 
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally{
            in.close();
            out.close();
        }
        return null;
    }
}

  測試代碼如下:

package test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestMyClassLoader {

    public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{
        //自定義類加載器的加載路徑
        MyClassLoader myClassLoader=new MyClassLoader("D:\\lib");
        //包名+類名
        Class c=myClassLoader.loadClass("com.test.Test");
        
        if(c!=null){
            Object obj=c.newInstance();
            Method method=c.getMethod("say", null);
            method.invoke(obj, null);
            System.out.println(c.getClassLoader().toString());
        }
    }
}

  輸出結果如下:

  

  自定義類加載器的作用:jvm自帶的三個加載器只能加載指定路徑下的類字節碼。如果某個情況下,我們需要加載應用程序之外的類文件呢?比如本地D盤下的,或者去加載網絡上的某個類文件,這種情況就可以使用自定義加載器了。

   參考網址:http://blog.csdn.net/briblue/article/details/54973413


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM