jvm學習筆記6:類載入器

賜我夢境~還我清醒發表於2020-11-17

1. 類與類載入器

對於任意一個類,都需要有載入它的類載入器和這個類本身一同確認其在java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。

2. 雙親委派機制

2.1 載入器分類

從java虛擬機器的角度來看,只存在兩種不同的類載入器:

  • 啟動類載入器(Bootstrap ClassLoader):C++語言實現,是java虛擬機器的一部分;
  • 其他所有的類載入器:這些都是有Java語言實現的,獨立於虛擬機器外部,並且都繼承自抽象類java.lang.ClassLoader。

從開發人員的角度來看,絕大部分Java程式都會使用以下三種類載入器,可以分為:

  • 啟動類載入器(Bootstrap ClassLoader):負責<JAVA_HOME>/lib或者被-Xbootclasspath引數指定的路徑。
  • 擴充套件類載入器(Extension ClassLoader):這個載入器由sun.misc.Launcher$ExtClassLoader實現。負責載入<JAVA_HOME>/lib/ext目錄以及被java.ext.dirs系統引數指定的類庫。
  • 應用類載入器(Application ClassLoader):這個類由sun.misc.Launcher$AppClassLoader實現。載入使用者類路徑(classPath)的類。

2.2 從原始碼來看雙親委派機制

雙親委派機制的圖示如下,其其工作流程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委派其父類載入器去完成,每一個層次的類載入器都是如此,因此最終所有的載入請求都應該傳遞給頂層的啟動類載入器,只有父類載入器無法完成這個載入請求時,子載入器才會嘗試自己去載入。
在這裡插入圖片描述
除了用C++實現的啟動類載入器,其餘所有的類載入器都要繼承抽象類ClassLoader方法,下面分析其中幾個關鍵方法的原始碼:

2.3.1 構造方法

    //指定一個父類載入器,指定null,則父類載入器為Bootstrap ClassLoader
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    //預設父類載入器為AppClassLoader
    protected ClassLoader() {
        this(checkCreateClassLoader(),  getSystemClassLoader());
    }

2.3.2 loadClass(String name, boolean resolve)

/**
* 雙親委派機制是通過loadClass方法實現的
*/
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) {
                        //如果有父類載入器,委派給父類載入器進行載入
                        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) {
                    //如果父類載入器都沒有載入成功,則呼叫自己的findClass 方法進行載入
                    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;
        }
    }

2.3.3 findClass(String name)

ClassLoder中的findClass方法是抽象方法,需要子類進行實現。ExtClassLoaderAppClassLoader都繼承了URLClassLoader,而URLClassLoader繼承了ClassLoder,並實現了findClass方法。

 protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        //找到對應的clss資源
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                            //根據class位元組流生產Class例項
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

2.3.4 defineClass

所有的類載入器最後都要掉這個方法,它內部會調本地方法,負責將class位元組流檔案轉換成Class例項。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        //先進行校驗,比如以java開頭的類名,不能載入。
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

3. 自定義類載入器

  • 如果要打破雙親委派機制,重寫loadClass()方法;
  • 否則,重寫findClass()方法即可。

3.1 自定義類載入器(載入指定目錄)

package com.lihui.study.jvm;

import java.io.*;

/**
 * @author ex_lihui4
 * @ClassName MyClassLoader
 * @Description 自定義一個普通的類載入器,通過dir執行相應的類目錄
 * @date 2020-11-17  11:50
 */

public class MyClassLoader extends ClassLoader {
    private String dir;

    public MyClassLoader(ClassLoader parent, String dir) {
        super(parent);
        this.dir = dir;
    }

    public MyClassLoader(String dir) {
        //如不指定父類載入器,
        this.dir = dir;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //拼接類路徑
        String className = name.substring(name.lastIndexOf(".") + 1)+".class";
        String classPth = "\\"+name.substring(0, name.lastIndexOf(".")).replace(".","\\")+"\\";
        String fileName = dir+classPth+className;
        //獲取二進位制資料
        File file = new File(fileName);
        try (FileInputStream is = new FileInputStream(file)){
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = is.read(buffer)) !=-1){
                os.write(buffer,0,length);
            }
            byte[] bytes = os.toByteArray();
            //返回類檔案
            return defineClass(name,bytes,0,bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

相關文章