Heim > Java > JavaBase > Hauptteil

Ausführliche Erklärung des Java-Klassenladers ClassLoader

angryTom
Freigeben: 2019-11-27 15:35:50
nach vorne
2241 Leute haben es durchsucht

Ausführliche Erklärung des Java-Klassenladers ClassLoader

Möglichkeiten, ClassLoader zu erhalten

1. Holen Sie sich den ClassLoader der aktuellen Klasse

clazz.getClassLoader()
Nach dem Login kopieren

2. Holen Sie sich den ClassLoader

Thread.currentThread().getContextClassLoader();
Nach dem Login kopieren

des aktuellen Thread-Kontexts. 3. Holen Sie sich den ClassLoader

ClassLoader.getSystemClassLoader()
Nach dem Login kopieren

des Systems. Holen Sie sich die ClassLoader-Quellcodeanalyse des Aufrufers

(Empfohlenes Lernen:

Java-Video-Tutorial

) Übersicht

Der Klassenlader wird verwendet Zum Laden von Klassenobjekten ist ClassLoader eine abstrakte Klasse. Wenn wir den

Binärnamen
einer Klasse erhalten, sollte der Klassenlader versuchen, die Daten zu finden oder zu generieren, aus denen die definierende Klasse besteht. Eine typische Strategie besteht darin, einen bestimmten Binärnamen in einen Dateinamen umzuwandeln und dann die diesem Dateinamen entsprechende Klassendatei aus dem Dateisystem zu lesen.

Jedes Klassenobjekt enthält einen Verweis auf den ClassLoader, der es definiert.

Das Klassenobjekt der Array-Klasse wird nicht vom Klassenlader erstellt, sondern automatisch von der JVM nach Bedarf während der Java-Laufzeit. Für den Klassenlader der Array-Klasse wird er über

Class.getClassLoader()

zurückgegeben. Dies ist derselbe wie der Klassenlader des Elementtyps im Array, wenn der Elementtyp im Array a ist nativer Typ, Die Array-Klasse verfügt nicht über einen Klassenlader [Code 1].

Die Anwendung implementiert eine Unterklasse von ClassLoader, um die Art und Weise zu erweitern, wie die JVM Klassen dynamisch lädt.

Klassenlader werden typischerweise von Sicherheitsmanagern verwendet, um Probleme mit Sicherheitsdomänen zu identifizieren.

Die ClassLoader-Klasse verwendet ein Delegationsmodell, um Klassen und Ressourcen zu finden. Jeder Instanz von ClassLoader ist ein übergeordneter ClassLoader zugeordnet. Wenn der ClassLoader aufgefordert wird, eine Klasse oder Ressource zu finden, versucht die ClassLoader-Instanz es Vor der Suche nach einer Klasse oder Ressource wird der übergeordnete Klassenlader mit der Vervollständigung beauftragt. Der integrierte Klassenlader der virtuellen Maschine, der als Startklassenlader bezeichnet wird, verfügt nicht über einen übergeordneten Lader, kann jedoch als übergeordneter Klassenlader eines Klassenladers verwendet werden [

Elterndelegierungsmechanismus

].

Ein Klassenlader, der das gleichzeitige Laden von Klassen unterstützt, wird als paralleler Klassenlader bezeichnet. Er muss sich während der Initialisierung über die Methode ClassLoader.registerAsParallelCapable

registrieren Standardmäßig, aber wenn die Unterklassen auch parallel geladen werden, müssen Sie die Unterklassen separat registrieren.

In einer Umgebung, in der das Delegationsmodell nicht streng hierarchisch ist, müssen Klassenlader parallelisiert werden, andernfalls führt das Laden von Klassen zu einem Deadlock, da die Sperre des Laders während des Klassenladevorgangs immer gehalten wird.

Normalerweise lädt die Java Virtual Machine plattformabhängig Klassen aus dem lokalen Dateisystem. In UNIX-Systemen lädt die virtuelle Maschine beispielsweise Klassen aus dem durch die CLASSPATH-Umgebung definierten Verzeichnis.

Einige Klassen stammen jedoch nicht aus Dateien; sie werden aus anderen Quellen wie dem Netzwerk bezogen oder von der Anwendung selbst erstellt [dynamische Proxys]. Die Methode defineClass konvertiert das Byte-Array in eine Instanz der Klasse. Diese Instanz der neu definierten Klasse kann mit

Class.newInstance

erstellt werden.
Methoden und Konstruktoren von Objekten, die von einem Klassenlader erstellt wurden, können auf andere Klassen verweisen. Um die referenzierte Klasse zu ermitteln, ruft die Java Virtual Machine die loadClass

des Klassenladers auf, der sie ursprünglich erstellt hat Klasse. Methode.

Binärname: Jeder Klassenname, der CalssLoader in Form eines String-Parameters bereitgestellt wird, muss ein Binärname sein, einschließlich der folgenden vier Fälle:

"java.lang. String" Normale Klasse

    "javax.swing.JSpinner$DefaultEditor" Interne Klasse
  • "java.security.KeyStore
  • (Builder)
  • FileBuilder$1" KeyStores interne Klasse Builder Die innere Klasse von FileBuilder Die erste anonyme innere Klasse
  • "java.net.URLClassLoader$3$1" Die dritte anonyme innere Klasse der URLClassLoader-Klasse Die erste anonyme innere Klasse
  • Code 1:
DriverManager.getCallerClassLoader
Nach dem Login kopieren

loadClass-Methode

Der Quellcode von LoadClass lautet wie folgt: Die LoadClass-Methode lädt standardmäßig eine Klasse mit einem angegebenen Binärnamen die folgende Reihenfolge:

a) Rufen Sie findLoadedClass(String) auf, um zu überprüfen, ob diese Klasse geladen ist

b) Rufen Sie die Methode „loadClass“ des übergeordneten Klassenladers auf. Die Startklasse wird aufgerufen. Der Loader

c) ruft die Methode findClass(String) auf, um

mithilfe der obigen Schritte zu finden. Wenn die Klasse gefunden wird und „resolve“ wahr ist, wird die Methode „resolveClass(Class )-Methode heißt

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
    }
}
Nach dem Login kopieren

findClass-Methode

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

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

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;
}
Nach dem Login kopieren

自定义类加载器

/**
 * 继承了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;
    }
}
Nach dem Login kopieren

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

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
Nach dem Login kopieren

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(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;
    }
}
Nach dem Login kopieren

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
Nach dem Login kopieren

修改一下测试代码,并删除工程下的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());
}
Nach dem Login kopieren

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a
Nach dem Login kopieren

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器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());
}
Nach dem Login kopieren

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

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
Nach dem Login kopieren

再改造一下

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());
}
Nach dem Login kopieren

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7
Nach dem Login kopieren

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());
}
Nach dem Login kopieren

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a
Nach dem Login kopieren

扩展:命名空间

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

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

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

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

Das obige ist der detaillierte Inhalt vonAusführliche Erklärung des Java-Klassenladers ClassLoader. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:cnblogs.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!