ホームページ >Java >&#&ベース >JavaクラスローダClassLoaderの詳細説明

JavaクラスローダClassLoaderの詳細説明

angryTom
angryTom転載
2019-11-27 15:35:502308ブラウズ

JavaクラスローダClassLoaderの詳細説明

ClassLoader の取得方法

1. 現在のクラスの ClassLoader を取得します

clazz.getClassLoader()

2. 現在のスレッド コンテキストの ClassLoader を取得します

Thread.currentThread().getContextClassLoader();

3. システムの ClassLoader を取得します

ClassLoader.getSystemClassLoader()

4. 呼び出し元の ClassLoader を取得します

DriverManager.getCallerClassLoader

ClassLoader ソース コード分析

(推奨学習: Java ビデオ チュートリアル )

概要

クラス ローダーが使用されますクラス Object をロードするには、ClassLoader は抽象クラスです。クラスの バイナリ名 が与えられた場合、クラス ローダーは、定義クラスを構成するデータを検索または生成しようとします。一般的な戦略は、指定されたバイナリ名をファイル名に変換し、このファイル名に対応するクラス ファイルをファイル システムから読み取ることです。

各 Class オブジェクトには、それを定義する ClassLoader への参照が含まれます。

配列クラスの Class オブジェクトは、クラス ローダーによって作成されませんが、Java 実行時に必要に応じて JVM によって自動的に作成されます。配列クラスのクラス ローダーの場合は、Class.getClassLoader() を通じて返されます。これは、配列内の要素の型がネイティブ型、配列クラスにはクラス ローダーがありません [コード 1]。

アプリケーションは ClassLoader のサブクラスを実装して、JVM がクラスを動的にロードする方法を拡張します。

クラス ローダーは通常、セキュリティ ドメインの問題を特定するためにセキュリティ管理者によって使用されます。

ClassLoader クラスは、委任モデルを使用してクラスとリソースを検索します。ClassLoader の各インスタンスには、親 ClassLoader が関連付けられています。ClassLoader がクラスまたはリソースの検索を要求されると、ClassLoader インスタンスは独自に試行します。クラスまたはリソースを検索する前に、その親クラス ローダーが検索を完了するために委任されます。スタートアップ クラス ローダーと呼ばれる仮想マシンの組み込みクラス ローダーには親クラス ローダーがありませんが、クラス ローダー [親委任メカニズム] の親クラス ローダーとして使用できます。

同時クラスの読み込みをサポートするクラス ローダーは、並列クラス ローダーと呼ばれます。初期化中に ClassLoader.registerAsParallelCapable メソッドを通じて自身を登録する必要があります。ClassLoader クラスは、次の方法で並列として登録されます。デフォルトですが、サブクラスも並行してロードされる場合は、サブクラスを個別に登録する必要があります。

委任モデルが厳密に階層的ではない環境では、クラス ローダーは並列である必要があります。そうしないと、クラス ロード プロセス中にローダーのロックが常に保持されるため、クラス ロードでデッドロックが発生します。

通常、Java 仮想マシンは、プラットフォームに依存する方法でローカル ファイル システムからクラスをロードします。たとえば、UNIX システムでは、仮想マシンは CLASSPATH 環境で定義されたディレクトリからクラスをロードします。
ただし、一部のクラスはファイルから取得されず、ネットワークなどの他のソースから取得されたり、[動的プロキシ] がアプリケーション自体によって構築されたりします。 defineClass(defineClass) メソッドは、バイト配列を Class のインスタンスに変換します。この新しく定義されたクラスのインスタンスは、Class.newInstance によって作成できます。

クラス ローダーによって作成されたオブジェクトのメソッドとコンストラクターは、他のクラスを参照する場合があります。参照されるクラスを決定するために、Java 仮想マシンは最初に作成したクラス ローダーの loadClass を呼び出します。クラス、メソッド。

バイナリ名: 文字列パラメータの形式で CalssLoader に提供されるクラス名は、次の 4 つの状況を含むバイナリ名である必要があります。

  • "java.lang.String" Normal class
  • "javax.swing.JSpinner$DefaultEditor" 内部クラス
  • "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore の内部 内部クラスクラス Builder の最初の匿名内部クラス FileBuilder
  • "java.net.URLClassLoader$3$1" URLClassLoader クラスの 3 番目の匿名内部クラス 最初の匿名内部クラス

コード 1:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 运行结果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 运行结果:null
    }
}

loadClass メソッド

loadClass のソース コードは次のとおりです。loadClass メソッドは、指定されたバイナリ名のクラスをロードします。デフォルトでは、クラスを検索します:

a) findLoadedClass(String) を呼び出して、このクラスがロードされているかどうかを確認します

b) 親クラス ローダーの loadClass メソッドを呼び出します。クラス ローダーが null の場合、呼び出されます。 クラス ローダーを開始します。

c) findClass(String) メソッドを呼び出して find

クラスが見つかり、resolve が true の場合は、上記の手順を使用します。 solveClass(Class) メソッドは

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;
  }
}

findClass メソッド

と呼ばれます。

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定义类加载器

/**
 * 继承了ClassLoader,这是一个自定义的类加载器
 * @author 夜的那种黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 类加载器名称,标识作用
     */
    private String classLoaderName;

    /**
     * 从磁盘读物字节码文件的扩展名
     */
    private String fileExtension = ".class";

    /**
     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(String classLoaderName) {
        // 将系统类加载器当做该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 创建一个类加载器对象,显示指定该类加载器的父加载器
     * 前提是需要有一个类加载器作为父加载器
     * @param parent 父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
     * 该方法会在检查完父类加载器之后被loadClass方法调用
     * 默认返回ClassNotFoundException异常
     * @param className 类名
     * @return Class的实例
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通过defineClass方法将字节数组转换为Class
         * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 从指定路径加载
     */
    private String path;

    ......
    
    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

扩展:命名空间

1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成

2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类

3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

php中文网,大量的免费Java入门教程,欢迎在线学习!  

以上がJavaクラスローダClassLoaderの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。