精华内容
下载资源
问答
  • ClassLoader

    2018-11-12 16:25:45
    Java 虚拟机中ClassLoader 相关简介 双亲委托机制 Android 中ClassLoader 简介
  • >se.jiderhamn.classloader-leak-prevention</ groupId > < artifactId >classloader-leak-prevention-servlet3</ artifactId > < version >2.7.0</ version > </ dependency > 如果您遇到 ...
  • 主要介绍了深入Spring Boot之ClassLoader的继承关系和影响,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 轻量级ndk实用程序,可帮助绕过Android N的classloader-namespace限制ndk_dlopen轻量级ndk实用程序,可帮助绕过Android N的classloader-namespace限制技术OSR(堆栈替换)支持x86,x86_64,armeabi-v7a(拇指和手臂...
  • 【图解版】深入分析ClassLoader类加载工作机制,从原理到JVM的装载过程,详情分析了ClassLoader加载类以及自定义类加载器的过程,不可用于商业用途,如有版权问题,请联系删除!
  • NULL 博文链接:https://ldbjakyo.iteye.com/blog/1046984
  • NULL 博文链接:https://fly-hyp.iteye.com/blog/296625
  • 使用Android的classloader加载器实现热更新,通过反射机制获取到源码的Elements数组替换classes.dex实现更新,只能重启软件进行更新,无法实现实时更新。
  • NULL 博文链接:https://lz12366.iteye.com/blog/735289
  •  Bootstrap ClassLoader是顶层的ClassLoader,它比较特殊,是用C++编写集成在JVM中的,是JVM启动的时候用来加载一些核心类的,比如:rt.jar,resources.jar,charsets.jar,jce.jar等,可以运行下面代码看都有哪些...
  • 主要介绍了Java classloader和namespace详细介绍的相关资料,需要的朋友可以参考下
  • java classloader

    2019-03-01 17:00:01
    NULL 博文链接:https://sbiigu.iteye.com/blog/260456
  • 热修复和插件化是目前比较热门的技术,要想更好的掌握它们需要了解ClassLoader,下面这篇文章主要给大家介绍了关于Android中自定义ClassLoader耗时问题追查的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧
  • 主要给大家介绍了关于Java运行时环境之ClassLoader类加载机制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 本篇文章主要给大家讲述了Java中ClassLoader类加载的原理以及用法总结,一起学习下。
  • 主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 破解java加密的ClassLoader.java,在classloader植入破解代码
  • classloader简单例子

    2019-03-23 01:23:45
    NULL 博文链接:https://andilyliao.iteye.com/blog/625682
  • classloader

    2010-08-31 15:35:53
    ...
  • Java ClassLoader

    2020-07-20 08:54:52
    Java ClassLoader is one of the crucial but rarely used components in project development. I have never extended ClassLoader in any of my projects. But, the idea of having my own ClassLoader that can c...

    Java ClassLoader is one of the crucial but rarely used components in project development. I have never extended ClassLoader in any of my projects. But, the idea of having my own ClassLoader that can customize the Java class loading is exciting.

    Java ClassLoader是项目开发中至关重要但很少使用的组件之一。 我从未在任何项目中扩展ClassLoader。 但是,拥有自己的可以自定义Java类加载的ClassLoader的想法令人兴奋。

    This article will provide an overview of Java ClassLoader and then move forward to create a custom class loader in Java.

    本文将概述Java ClassLoader,然后继续使用Java创建自定义类加载器。

    什么是Java ClassLoader? (What is Java ClassLoader?)

    We know that Java Program runs on Java Virtual Machine (JVM). When we compile a Java Class, JVM creates the bytecode, which is platform and machine-independent. The bytecode is stored in a .class file. When we try to use a class, the ClassLoader loads it into the memory.

    我们知道Java程序在Java虚拟机 (JVM)上运行。 当我们编译Java类时,JVM将创建字节码,该字节码与平台和机器无关。 字节码存储在.class文件中 。 当我们尝试使用一个类时,ClassLoader将其加载到内存中。

    内置的ClassLoader类型 (Built-in ClassLoader Types)

    There are three types of built-in ClassLoader in Java.

    Java内置了三种类型的内置ClassLoader。

    1. Bootstrap Class Loader – It loads JDK internal classes. It loads rt.jar and other core classes for example java.lang.* package classes.

      Bootstrap类加载器 –加载JDK内部类。 它加载rt.jar和其他核心类,例如java.lang。*包类。
    2. Extensions Class Loader – It loads classes from the JDK extensions directory, usually $JAVA_HOME/lib/ext directory.

      扩展类加载器 –它从JDK扩展目录(通常为$ JAVA_HOME / lib / ext目录)中加载类。
    3. System Class Loader – This classloader loads classes from the current classpath. We can set classpath while invoking a program using -cp or -classpath command line option.

      系统类加载器该类加载器从当前类路径加载类。 我们可以在使用-cp或-classpath命令行选项调用程序时设置classpath。

    ClassLoader层次结构 (ClassLoader Hierarchy)

    ClassLoader is hierarchical in loading a class into memory. Whenever a request is raised to load a class, it delegates it to the parent classloader. This is how uniqueness is maintained in the runtime environment. If the parent class loader doesn’t find the class then the class loader itself tries to load the class.

    ClassLoader在将类加载到内存中是分层的。 每当提出加载类的请求时,它都会将其委托给父类加载器。 这就是在运行时环境中保持唯一性的方式。 如果父类加载器找不到该类,则该类加载器本身将尝试加载该类。

    Let’s understand this by executing the below java program.

    让我们通过执行以下java程序来了解这一点。

    package com.journaldev.classloader;
    
    public class ClassLoaderTest {
    
        public static void main(String[] args) {
    
            System.out.println("class loader for HashMap: "
                    + java.util.HashMap.class.getClassLoader());
            System.out.println("class loader for DNSNameService: "
                    + sun.net.spi.nameservice.dns.DNSNameService.class
                            .getClassLoader());
            System.out.println("class loader for this class: "
                    + ClassLoaderTest.class.getClassLoader());
    
            System.out.println(com.mysql.jdbc.Blob.class.getClassLoader());
    
        }
    
    }

    Output:

    输出:

    class loader for HashMap: null
    class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093
    class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37
    sun.misc.Launcher$AppClassLoader@64cbbe37

    Java ClassLoader如何工作? (How Java ClassLoader Works?)

    Let’s understand the working of class loaders from the above program output.

    让我们从上面的程序输出中了解类加载器的工作方式。

    • The java.util.HashMap ClassLoader is coming as null, which reflects Bootstrap ClassLoader. The DNSNameService class ClassLoader is ExtClassLoader. Since the class itself is in CLASSPATH, System ClassLoader loads it.

      java.util.HashMap ClassLoader以null形式出现,这反映了Bootstrap ClassLoader。 DNSNameService类ClassLoader是ExtClassLoader。 由于类本身位于CLASSPATH中,因此System ClassLoader会加载它。
    • When we are trying to load HashMap, our System ClassLoader delegates it to the Extension ClassLoader. The extension class loader delegates it to the Bootstrap ClassLoader. The bootstrap class loader finds the HashMap class and loads it into the JVM memory.

      当我们尝试加载HashMap时,我们的System ClassLoader将其委托给Extension ClassLoader。 扩展类加载器将其委托给Bootstrap ClassLoader。 引导类加载器会找到HashMap类并将其加载到JVM内存中。
    • The same process is followed for the DNSNameService class. But, the Bootstrap ClassLoader is not able to locate it since it’s in $JAVA_HOME/lib/ext/dnsns.jar. Hence, it gets loaded by Extensions Classloader.

      DNSNameService类遵循相同的过程。 但是,由于Bootstrap ClassLoader位于$JAVA_HOME/lib/ext/dnsns.jar因此无法找到它。 因此,它由扩展类加载器加载。
    • The Blob class is included in the MySql JDBC Connector jar (mysql-connector-java-5.0.7-bin.jar), which is present in the build path of the project. It’s also getting loaded by the System Classloader.

      Blob类包含在MySql JDBC连接器jar(mysql-connector-java-5.0.7-bin.jar)中,该jar存在于项目的构建路径中。 系统类加载器也正在加载它。
    • The classes loaded by a child class loader have visibility into classes loaded by its parent class loaders. So classes loaded by System Classloader have visibility into classes loaded by Extensions and Bootstrap Classloader.

      子类加载器加载的类可以查看其父类加载器加载的类。 因此,由System Classloader加载的类可以查看由Extensions和Bootstrap Classloader加载的类。
    • If there are sibling class loaders then they can’t access classes loaded by each other.

      如果有同级类加载器,则它们将无法访问彼此加载的类。

    为什么要用Java编写自定义ClassLoader? (Why write a Custom ClassLoader in Java?)

    Java default ClassLoader can load classes from the local file system, which is good enough for most of the cases. But, if you are expecting a class at the runtime or from the FTP server or via third party web service at the time of loading the class, then you have to extend the existing class loader. For example, AppletViewers load the classes from a remote web server.

    Java默认的ClassLoader可以从本地文件系统加载类,这在大多数情况下已经足够了。 但是,如果在加载类时希望在运行时或从FTP服务器或通过第三方Web服务获取类,则必须扩展现有的类加载器。 例如,AppletViewers从远程Web服务器加载类。

    Java ClassLoader方法 (Java ClassLoader Methods)

    • When JVM requests for a class, it invokes loadClass() function of the ClassLoader by passing the fully classified name of the Class.

      JVM请求一个类时,它将通过传递类的完全分类名称来调用ClassLoader的loadClass()函数。
    • The loadClass() function calls the findLoadedClass() method to check that the class has been already loaded or not. It’s required to avoid loading the same class multiple times.

      loadClass()函数调用findLoadedClass()方法以检查是否已加载该类。 需要避免多次加载同一类。
    • If the Class is not already loaded, then it will delegate the request to parent ClassLoader to load the class.

      如果尚未加载该类,则它将把请求委托给父ClassLoader来加载该类。
    • If the parent ClassLoader doesn’t find the class then it will invoke findClass() method to look for the classes in the file system.

      如果父类ClassLoader找不到该类,则它将调用findClass()方法在文件系统中查找这些类。

    Java自定义ClassLoader示例 (Java Custom ClassLoader Example)

    We will create our own ClassLoader by extending the ClassLoader class and overriding the loadClass(String name) method.

    我们将通过扩展ClassLoader类并覆盖loadClass(String name)方法来创建自己的ClassLoader。

    If the class name will start from com.journaldev then we will load it using our custom class loader or else we will invoke the parent ClassLoader loadClass() method to load the class.

    如果类名将从com.journaldev开始,则我们将使用自定义类加载器加载它,否则我们将调用父ClassLoader loadClass()方法加载该类。

    1. CCLoader.java (1. CCLoader.java)

    This is our custom class loader with below methods.

    这是带有以下方法的自定义类加载器。

    1. private byte[] loadClassFileData(String name): This method will read the class file from file system to byte array.

      private byte[] loadClassFileData(String name) :此方法将从文件系统读取类文件到字节数组。
    2. private Class<?> getClass(String name): This method will call the loadClassFileData() function and by invoking the parent defineClass() method, it will generate the Class and return it.

      private Class<?> getClass(String name) :此方法将调用loadClassFileData()函数,并通过调用父defineClass()方法,将生成Class并返回它。
    3. public Class<?> loadClass(String name): This method is responsible for loading the class. If the class name starts with com.journaldev (Our sample classes) then it will load it using getClass() method or else it will invoke the parent loadClass() function to load it.

      public Class<?> loadClass(String name) :此方法负责加载类。 如果类名以com.journaldev(我们的示例类)开头,则它将使用getClass()方法加载它,否则它将调用父loadClass()函数来加载它。
    4. public CCLoader(ClassLoader parent): This is the constructor, which is responsible for setting the parent ClassLoader.

      public CCLoader(ClassLoader parent) :这是构造函数,负责设置父ClassLoader。
    import java.io.DataInputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
     
    /**
     * Our Custom ClassLoader to load the classes. Any class in the com.journaldev
     * package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader.
     *
     */
    public class CCLoader extends ClassLoader {
     
        /**
         * This constructor is used to set the parent ClassLoader
         */
        public CCLoader(ClassLoader parent) {
            super(parent);
        }
     
        /**
         * Loads the class from the file system. The class file should be located in
         * the file system. The name should be relative to get the file location
         *
         * @param name
         *            Fully Classified name of the class, for example, com.journaldev.Foo
         */
        private Class getClass(String name) throws ClassNotFoundException {
            String file = name.replace('.', File.separatorChar) + ".class";
            byte[] b = null;
            try {
                // This loads the byte code data from the file
                b = loadClassFileData(file);
                // defineClass is inherited from the ClassLoader class
                // that converts byte array into a Class. defineClass is Final
                // so we cannot override it
                Class c = defineClass(name, b, 0, b.length);
                resolveClass(c);
                return c;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
     
        /**
         * Every request for a class passes through this method. If the class is in
         * com.journaldev package, we will use this classloader or else delegate the
         * request to parent classloader.
         *
         *
         * @param name
         *            Full class name
         */
        @Override
        public Class loadClass(String name) throws ClassNotFoundException {
            System.out.println("Loading Class '" + name + "'");
            if (name.startsWith("com.journaldev")) {
                System.out.println("Loading Class using CCLoader");
                return getClass(name);
            }
            return super.loadClass(name);
        }
     
        /**
         * Reads the file (.class) into a byte array. The file should be
         * accessible as a resource and make sure that it's not in Classpath to avoid
         * any confusion.
         *
         * @param name
         *            Filename
         * @return Byte array read from the file
         * @throws IOException
         *             if an exception comes in reading the file
         */
        private byte[] loadClassFileData(String name) throws IOException {
            InputStream stream = getClass().getClassLoader().getResourceAsStream(
                    name);
            int size = stream.available();
            byte buff[] = new byte[size];
            DataInputStream in = new DataInputStream(stream);
            in.readFully(buff);
            in.close();
            return buff;
        }
    }

    2. CCRun.java (2. CCRun.java)

    This is our test class with the main function. We are creating an instance of our ClassLoader and loading sample classes using its loadClass() method.

    这是带有主要功能的测试类。 我们正在创建ClassLoader的实例,并使用其loadClass()方法加载示例类。

    After loading the class, we are using Java Reflection API to invoke its methods.

    加载该类之后,我们将使用Java Reflection API来调用其方法。

    import java.lang.reflect.Method;
     
    public class CCRun {
     
        public static void main(String args[]) throws Exception {
            String progClass = args[0];
            String progArgs[] = new String[args.length - 1];
            System.arraycopy(args, 1, progArgs, 0, progArgs.length);
    
            CCLoader ccl = new CCLoader(CCRun.class.getClassLoader());
            Class clas = ccl.loadClass(progClass);
            Class mainArgType[] = { (new String[0]).getClass() };
            Method main = clas.getMethod("main", mainArgType);
            Object argsArray[] = { progArgs };
            main.invoke(null, argsArray);
    
            // Below method is used to check that the Foo is getting loaded
            // by our custom class loader i.e CCLoader
            Method printCL = clas.getMethod("printCL", null);
            printCL.invoke(null, new Object[0]);
        }
     
    }

    3. Foo.java和Bar.java (3. Foo.java and Bar.java)

    These are our test classes that are getting loaded by our custom classloader. They have a printCL() method, which is getting invoked to print the ClassLoader information.

    这些是由我们的自定义类加载器加载的测试类。 它们具有一个printCL()方法,该方法将被调用以打印ClassLoader信息。

    Foo class will be loaded by our custom class loader. Foo uses Bar class, so Bar class will also be loaded by our custom class loader.

    Foo类将由我们的自定义类加载器加载。 Foo使用Bar类,因此Bar类也将由我们的自定义类加载器加载。

    package com.journaldev.cl;
     
    public class Foo {
        static public void main(String args[]) throws Exception {
            System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]);
            Bar bar = new Bar(args[0], args[1]);
            bar.printCL();
        }
     
        public static void printCL() {
            System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
        }
    }
    package com.journaldev.cl;
     
    public class Bar {
     
        public Bar(String a, String b) {
            System.out.println("Bar Constructor >>> " + a + " " + b);
        }
     
        public void printCL() {
            System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader());
        }
    }

    4. Java自定义ClassLoader执行步骤 (4. Java Custom ClassLoader Execution Steps)

    First of all, we will compile all the classes through the command line. After that, we will run the CCRun class by passing three arguments. The first argument is the fully classified name for Foo class that will get loaded by our class loader. Other two arguments are passed along to the Foo class main function and Bar constructor. The execution steps and the output will be like below.

    首先,我们将通过命令行编译所有类。 之后,我们将通过传递三个参数来运行CCRun类。 第一个参数是Foo类的完全分类名称,它将由我们的类加载器加载。 其他两个参数将传递给Foo类的主函数和Bar构造函数。 执行步骤和输出将如下所示。

    $ javac -cp . com/journaldev/cl/Foo.java
    $ javac -cp . com/journaldev/cl/Bar.java
    $ javac CCLoader.java
    $ javac CCRun.java
    CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;
    cast to java.lang.Class<?> for a varargs call
    cast to java.lang.Class<?>[] for a non-varargs call and to suppress this warning
    Method printCL = clas.getMethod("printCL", null);
    ^
    1 warning
    $ java CCRun com.journaldev.cl.Foo 1212 1313
    Loading Class 'com.journaldev.cl.Foo'
    Loading Class using CCLoader
    Loading Class 'java.lang.Object'
    Loading Class 'java.lang.String'
    Loading Class 'java.lang.Exception'
    Loading Class 'java.lang.System'
    Loading Class 'java.lang.StringBuilder'
    Loading Class 'java.io.PrintStream'
    Foo Constructor >>> 1212 1313
    Loading Class 'com.journaldev.cl.Bar'
    Loading Class using CCLoader
    Bar Constructor >>> 1212 1313
    Loading Class 'java.lang.Class'
    Bar ClassLoader: CCLoader@71f6f0bf
    Foo ClassLoader: CCLoader@71f6f0bf
    $

    If you look at the output, it’s trying to load com.journaldev.cl.Foo class. Since it’s extending java.lang.Object class, it’s trying to load Object class first.

    如果查看输出,它将尝试加载com.journaldev.cl.Foo类。 由于它扩展了java.lang.Object类,因此它尝试首先加载Object类。

    So the request is coming to CCLoader loadClass method, which is delegating it to the parent class. So the parent class loaders are loading the Object, String, and other java classes.

    因此,该请求即将到达CCLoader loadClass方法,该方法将其委派给父类。 因此,父类加载器正在加载Object,String和其他Java类。

    Our ClassLoader is only loading Foo and Bar class from the file system. It’s clear from the output of the printCL() function.

    我们的ClassLoader仅从文件系统加载Foo和Bar类。 从printCL()函数的输出中可以很清楚地看到。

    We can change the loadClassFileData() functionality to read the byte array from FTP Server or by invoking any third party service to get the class byte array on the fly.

    我们可以更改loadClassFileData()功能以从FTP服务器读取字节数组,或者通过调用任何第三方服务来即时获取类字节数组。

    I hope that the article will be useful in understanding Java ClassLoader working and how we can extend it to do a lot more than just taking it from the file system.

    我希望这篇文章对理解Java ClassLoader的工作以及如何扩展它的作用不仅仅适用于从文件系统中获取知识,这将是有益的。

    将自定义ClassLoader设置为默认ClassLoader (Making Custom ClassLoader as Default ClassLoader)

    We can make our custom class loader as the default one when JVM starts by using Java Options.

    通过使用Java选项,可以在JVM启动时将自定义类装入器作为默认装入器。

    For example, I will run the ClassLoaderTest program once again after providing the java classloader option.

    例如,在提供java classloader选项之后,我将再次运行ClassLoaderTest程序。

    $ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java
    $ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest
    Loading Class 'com.journaldev.classloader.ClassLoaderTest'
    Loading Class using CCLoader
    Loading Class 'java.lang.Object'
    Loading Class 'java.lang.String'
    Loading Class 'java.lang.System'
    Loading Class 'java.lang.StringBuilder'
    Loading Class 'java.util.HashMap'
    Loading Class 'java.lang.Class'
    Loading Class 'java.io.PrintStream'
    class loader for HashMap: null
    Loading Class 'sun.net.spi.nameservice.dns.DNSNameService'
    class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457
    class loader for this class: CCLoader@38503429
    Loading Class 'com.mysql.jdbc.Blob'
    sun.misc.Launcher$AppClassLoader@2f94ca6c
    $

    The CCLoader is loading the ClassLoaderTest class because its in com.journaldev package.

    CCLoader正在加载ClassLoaderTest类,因为它位于com.journaldev程序包中。

    GitHub Repository. GitHub Repository下载ClassLoader示例代码。

    翻译自: https://www.journaldev.com/349/java-classloader

    展开全文
  • 深入理解ClassLoader工作机制

    千次阅读 2019-05-05 20:30:50
    ClassLoader类加载器可以说是Java中必学内容之一,无论是想要去研究Concurrent包、Unsafe,还是深入学习Spark等分布式计算框架,都必须对此有一定的理解。笔者在写之前也只了解了皮毛,想通过这篇文章,结合一些书籍...

    前言

    ClassLoader类加载器可以说是Java中必学内容之一,无论是想要去研究Concurrent包、Unsafe,还是深入学习Spark等分布式计算框架,都必须对此有一定的理解。笔者在写之前也只了解了皮毛,想通过这篇文章,结合一些书籍和博客,加深对ClassLoader的理解,并分享一下。

    什么是class文件?

    xxx.class想必不陌生,JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言, class文件就相当于一个接口,处于中间的位置, 对于理解整个体系有着承上启下的作用。 如图所示:

    在这里插入图片描述
    相关JVM的基础,可以看下–>初探JVM原理与结构
    深入理解Java Class文件

    ClassLoader简单认识

    ClassLoader的具体作用就是将class文件加载到JVM虚拟机中去,程序就可以正确运行了。但是,JVM启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是,一次性加载那么多jar包那么多class,那内存不崩溃。

    我们知道,java是一种动态语言。那么怎样理解这个“动态”呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?我们都知道JVM执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于class文件中)。这就要求虚拟机在真正执行字节码之前,先把相关的class文件加载到内存中。虚拟机不是一次性加载所有需要的class文件,因为它在执行的时候根本不会知道以后会用到哪些class文件。它是每用到一个类,就会在运行时“动态地”加载和这个类相关的class文件。这就是java被称之为动态性语言的根本原因。除了动态加载类之外,还会动态的初始化类,对类进行动态链接。

    在JVM中负责对类进行加载的正是本文要介绍的类加载器(ClassLoader),所以,类加载器是JVM不可或缺的重要组件。

    JAVA ClassLoader分类

    Java语言系统自带有三个类加载器:

    Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。

    Extension ClassLoader 主要负责加载Java的扩展类库,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。

    Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。

    我们上面简单介绍了3个ClassLoader。说明了它们加载的路径。并且还提到了-Xbootclasspath和-D java.ext.dirs这两个虚拟机参数选项。

    自定义的ClassLoader 扩展Java虚拟机获取Class数据的能力,可以加载指定路径下的class文件,一般继承ClassLoader重写findClass()方法即可一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。因为这样就能够保证它能访问系统内置加载器加载成功的class文件。

    各个ClassLoader的层次和功能如下图所示,从ClassLoader的层次自顶往下为启动类加载器、扩展类加载器、应用类加载器和自定义类加载器。其中,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器。当系统需要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统需要加载一个类时,会从顶层类开始加载,依次向下尝试,直到成功。
    在这里插入图片描述

    加载顺序?

    我们看到了系统的3个类加载器,但我们可能不知道具体哪个先行呢?
    我可以先告诉你答案

    1. Bootstrap ClassLoader
    2. Extension ClassLoader
    3. AppClassLoader

    为了更好的理解,我们可以查看源码。
    看sun.misc.Launcher,它是一个java虚拟机的入口应用。
    源码这边就不贴了,有兴趣可以自行去看Launcher,我们可以得到结论BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是对应环境属性sun.boot.class.path、java.ext.dirs和java.class.path来加载资源文件的。

    实践

    编写一个ClassLoaderTest.java:

    public class ClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader cl = ClassLoaderTest.class.getClassLoader();
            System.out.println("ClassLoader is:" + cl.toString());
        }
    }
    

    执行得到结果:
    在这里插入图片描述
    也就是说明ClassLoaderTest.class文件是由AppClassLoader加载的。

    那么基础类型呢? 如int、boolean:

    public class ClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader cl = ClassLoaderTest.class.getClassLoader();
            System.out.println("ClassLoader is:" + cl.toString());
            cl=int.class.getClassLoader();
            System.out.println("ClassLoader is:" + cl.toString());
        }
    }
    

    在这里插入图片描述
    提示的是空指针,意思是int.class这类基础类没有类加载器加载?
    当然不是!
    int.class是由Bootstrap ClassLoader加载的。它是完全由C++代码实现的,并且在Java中没有对象与之对应,它也是虚拟机的核心组件。Extension ClassLoader和AppClassLoader都有对应的Java对象可供使用。
    无法在Java代码中直接访问Bootstrap ClassLoader,因为这是一个纯C实现,因此任何加载在Bootstrap ClassLoader中的类是无法获得其ClassLoader实例的。由于int、String等基础类型属于Java核心类,由启动类加载器加载,故以上代码返回的是null。
    JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例,并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释通过ExtClassLoader的getParent方法获取为Null的现象。

    尝试一下:

    public class ClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader cl = ClassLoaderTest.class.getClassLoader();
            System.out.println("ClassLoader is:" + cl.toString());
            System.out.println("ClassLoader is:" + cl.getParent().toString());
            System.out.println("ClassLoader is:" + cl.getParent().getParent().toString());
        }
    }
    

    在这里插入图片描述

    双亲委托

    系统中的ClassLoader在协同工作时,默认会使用双亲委托模式。即在类加载的时候,系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。
    注意:双亲为null有两种情况:第一,其双亲就是启动类加载器;第二,当前加载器就是启动类加载器。判断类是否加载时,应用类加载器会顺着双亲路径往上判断,直到启动类加载器。但是启动类加载器不会往下询问,这个委托是单向的。

    双亲委托模型好处

    因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

    loadClass()

    JDK文档中是这样写的,通过指定的全限定类名加载class,它通过同名的loadClass(String, boolean)方法。
    执行findLoadedClass(String)去检测这个class是不是已经加载过了。

    执行父加载器的loadClass方法。如果父加载器为null,则JVM内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
    如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

    如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。 我们可以从源代码看出这个步骤。

    源码如下:

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

    双亲委托模式的弊端

    前文提到,检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。
    通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中提供了一个接口,该接口需要在应用类中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

    总结

    1. ClassLoader用来加载class文件的。
    2. 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。
    3. 可以自定义ClassLoader一般覆盖findClass()方法。
    4. ContextClassLoader与线程相关,可以获取和设置,可以绕过双亲委托的机制。
    展开全文
  • 深入理解Java类加载器(ClassLoader)

    万次阅读 多人点赞 2017-06-26 09:34:08
    从图可以看出顶层的类加载器是ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),这里我们主要介绍ClassLoader中几个比较重要的方法。 loadClass(String) 该...

    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
    http://blog.csdn.net/javazejian/article/details/73413292
    出自【zejian的博客】

    关联文章:

    深入理解Java类型信息(Class对象)与反射机制

    深入理解Java枚举类型(enum)

    深入理解Java注解类型(@Annotation)

    深入理解Java类加载器(ClassLoader)

    深入理解Java并发之synchronized实现原理

    Java并发编程-无锁CAS与Unsafe类及其并发包Atomic

    深入理解Java内存模型(JMM)及volatile关键字

    剖析基于并发AQS的重入锁(ReetrantLock)及其Condition实现原理

    剖析基于并发AQS的共享锁的实现(基于信号量Semaphore)

    并发之阻塞队列LinkedBlockingQueue与ArrayBlockingQueue

    本篇博文主要是探讨类加载器,同时在本篇中列举的源码都基于Java8版本,不同的版本可能有些许差异。主要内容如下

    文章目录


    # 类加载的机制的层次结构
    每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为".class"的文件,".class"文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的".class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:

    • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

    • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

    • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。

    • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

    这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),下面分别介绍
    ##启动(Bootstrap)类加载器
    启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

    ##扩展(Extension)类加载器
    扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

    //ExtClassLoader类中获取路径的代码
    private static File[] getExtDirs() {
         //加载<JAVA_HOME>/lib/ext目录中的类库
         String s = System.getProperty("java.ext.dirs");
         File[] dirs;
         if (s != null) {
             StringTokenizer st =
                 new StringTokenizer(s, File.pathSeparator);
             int count = st.countTokens();
             dirs = new File[count];
             for (int i = 0; i < count; i++) {
                 dirs[i] = new File(st.nextToken());
             }
         } else {
             dirs = new File[0];
         }
         return dirs;
     }
    
    

    ##系统(System)类加载器
    也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
      在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式,下面我们进一步了解它。
    #理解双亲委派模式

    ##双亲委派模式工作原理
    双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:

    双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢?

    ##双亲委派模式优势
    采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常

    java.lang.SecurityException: Prohibited package name: java.lang
    

    所以无论如何都无法加载成功的。下面我们从代码层面了解几个Java中定义的类加载器及其双亲委派模式的实现,它们类图关系如下

    从图可以看出顶层的类加载器是ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),这里我们主要介绍ClassLoader中几个比较重要的方法。

    • loadClass(String)

      该方法加载指定名称(包括包名)的二进制类型,该方法在JDK1.2之后不再建议用户重写但用户可以直接调用该方法,loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现,其源码如下,loadClass(String name, boolean resolve)是一个重载方法,resolve参数代表是否生成class对象的同时进行解析相关操作。:

      protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 先从缓存查找该class对象,找到就不用重新加载
                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
                        // 如果都没有找到,则通过自定义实现的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(c);
                }
                return c;
            }
        }
      

      正如loadClass方法所展示的,当类加载请求到来时,先从缓存中查找该类对象,如果存在直接返回,如果不存在则交给该类加载去的父加载器去加载,倘若没有父加载则交给顶级启动类加载器去加载,最后倘若仍没有找到,则使用findClass()方法去加载(关于findClass()稍后会进一步介绍)。从loadClass实现也可以知道如果不想重新定义加载类的规则,也没有复杂的逻辑,只想在运行时加载自己指定的类,那么我们可以直接使用this.getClass().getClassLoder.loadClass("className"),这样就可以直接调用ClassLoader的loadClass方法获取到class对象。

    • findClass(String)
      在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知,findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的(稍后会分析),ClassLoader类中findClass()方法源码如下:

      //直接抛出异常
      protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
      }
      
    • defineClass(byte[] b, int off, int len)
      defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中已实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象,defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象,简单例子如下:

      protected Class<?> findClass(String name) throws ClassNotFoundException {
      	  // 获取类的字节数组
            byte[] classData = getClassData(name);  
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
      	      //使用defineClass生成class对象
                return defineClass(name, classData, 0, classData.length);
            }
        }
      

    需要注意的是,如果直接调用defineClass()方法生成类的Class对象,这个类的Class对象并没有解析(也可以理解为链接阶段,毕竟解析是链接的最后一步),其解析操作需要等待初始化阶段进行。

    • resolveClass(Class≺?≻ c)
      使用该方法可以使用类的Class对象创建完成也同时被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。

    上述4个方法是ClassLoader类中的比较重要的方法,也是我们可能会经常用到的方法。接看SercureClassLoader扩展了 ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要指对class源码的访问权限)的方法,一般我们不会直接跟这个类打交道,更多是与它的子类URLClassLoader有所关联,前面说过,ClassLoader是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource()等。而URLClassLoader这个实现类为这些方法提供了具体的实现,并新增了URLClassPath类协助取得Class字节码流等功能,在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁,下面是URLClassLoader的类图(利用IDEA生成的类图)

    从类图结构看出URLClassLoader中存在一个URLClassPath类,通过这个类就可以找到要加载的字节码流,也就是说URLClassPath类负责找到要加载的字节码,再读取成字节流,最后通过defineClass()方法创建类的Class对象。从URLClassLoader类的结构图可以看出其构造方法都有一个必须传递的参数URL[],该参数的元素是代表字节码文件的路径,换句话说在创建URLClassLoader对象时必须要指定这个类加载器的到那个目录下找class文件。同时也应该注意URL[]也是URLClassPath类的必传参数,在创建URLClassPath对象时,会根据传递过来的URL数组中的路径判断是文件还是jar包,然后根据不同的路径创建FileLoader或者JarLoader或默认Loader类去加载相应路径下的class文件,而当JVM调用findClass()方法时,就由这3个加载器中的一个将class文件的字节码流加载到内存中,最后利用字节码流创建类的class对象。请记住,如果我们在定义类加载器时选择继承ClassLoader类而非URLClassLoader,必须手动编写findclass()方法的加载逻辑以及获取字节码流的逻辑。了解完URLClassLoader后接着看看剩余的两个类加载器,即拓展类加载器ExtClassLoader和系统类加载器AppClassLoader,这两个类都继承自URLClassLoader,是sun.misc.Launcher的静态内部类。sun.misc.Launcher主要被系统用于启动主应用程序,ExtClassLoader和AppClassLoader都是由sun.misc.Launcher创建的,其类主要类结构如下:

    它们间的关系正如前面所阐述的那样,同时我们发现ExtClassLoader并没有重写loadClass()方法,这足矣说明其遵循双亲委派模式,而AppClassLoader重载了loadCass()方法,但最终调用的还是父类loadClass()方法,因此依然遵守双亲委派模式,重载方法源码如下:

     /**
      * Override loadClass 方法,新增包权限检测功能
      */
     public Class loadClass(String name, boolean resolve)
         throws ClassNotFoundException
     {
         int i = name.lastIndexOf('.');
         if (i != -1) {
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 sm.checkPackageAccess(name.substring(0, i));
             }
         }
         //依然调用父类的方法
         return (super.loadClass(name, resolve));
     }
    

    其实无论是ExtClassLoader还是AppClassLoader都继承URLClassLoader类,因此它们都遵守双亲委托模型,这点是毋庸置疑的。ok,到此我们对ClassLoader、URLClassLoader、ExtClassLoader、AppClassLoader以及Launcher类间的关系有了比较清晰的了解,同时对一些主要的方法也有一定的认识,这里并没有对这些类的源码进行详细的分析,毕竟没有那个必要,因为我们主要弄得类与类间的关系和常用的方法同时搞清楚双亲委托模式的实现过程,为编写自定义类加载器做铺垫就足够了。ok,前面出现了很多父类加载器的说法,但每个类加载器的父类到底是谁,一直没有阐明,下面我们就通过代码验证的方式来阐明这答案。
    ##类加载器间的关系
    我们进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点

    • 启动类加载器,由C++实现,没有父类。

    • 拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null

    • 系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

    • 自定义类加载器,父类加载器肯定为AppClassLoader。

    下面我们通过程序来验证上述阐述的观点

    /**
     * Created by zejian on 2017/6/18.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    //自定义ClassLoader,完整代码稍后分析
    class FileClassLoader extends  ClassLoader{
        private String rootDir;
    
        public FileClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
        // 编写获取类的字节码并创建class对象的逻辑
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
           //...省略逻辑代码
        }
    	//编写读取字节流的方法
        private byte[] getClassData(String className) {
            // 读取类文件的字节
            //省略代码....
        }
    }
    
    public class ClassLoaderTest {
    
        public static void main(String[] args) throws ClassNotFoundException {
           
    			 FileClassLoader loader1 = new FileClassLoader(rootDir);
    			
    			  System.out.println("自定义类加载器的父加载器: "+loader1.getParent());
    			  System.out.println("系统默认的AppClassLoader: "+ClassLoader.getSystemClassLoader());
    			  System.out.println("AppClassLoader的父类加载器: "+ClassLoader.getSystemClassLoader().getParent());
    			  System.out.println("ExtClassLoader的父类加载器: "+ClassLoader.getSystemClassLoader().getParent().getParent());
    			
    			/**
    			输出结果:
    			    自定义类加载器的父加载器: sun.misc.Launcher$AppClassLoader@29453f44
    			    系统默认的AppClassLoader: sun.misc.Launcher$AppClassLoader@29453f44
    			    AppClassLoader的父类加载器: sun.misc.Launcher$ExtClassLoader@6f94fa3e
    			    ExtClassLoader的父类加载器: null
    			*/
    
        }
    }
    

    代码中,我们自定义了一个FileClassLoader,这里我们继承了ClassLoader而非URLClassLoader,因此需要自己编写findClass()方法逻辑以及加载字节码的逻辑,关于自定义类加载器我们稍后会分析,这里仅需要知道FileClassLoader是自定义加载器即可,接着在main方法中,通过ClassLoader.getSystemClassLoader()获取到系统默认类加载器,通过获取其父类加载器及其父父类加载器,同时还获取了自定义类加载器的父类加载器,最终输出结果正如我们所预料的,AppClassLoader的父类加载器为ExtClassLoader,而ExtClassLoader没有父类加载器。如果我们实现自己的类加载器,它的父加载器都只会是AppClassLoader。这里我们不妨看看Lancher的构造器源码

    public Launcher() {
            // 首先创建拓展类加载器
            ClassLoader extcl;
            try {
                extcl = ExtClassLoader.getExtClassLoader();
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create extension class loader");
            }
    
            // Now create the class loader to use to launch the application
            try {
    	        //再创建AppClassLoader并把extcl作为父加载器传递给AppClassLoader
                loader = AppClassLoader.getAppClassLoader(extcl);
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create application class loader");
            }
    
            //设置线程上下文类加载器,稍后分析
            Thread.currentThread().setContextClassLoader(loader);
    //省略其他没必要的代码......
            }
        }
    

    显然Lancher初始化时首先会创建ExtClassLoader类加载器,然后再创建AppClassLoader并把ExtClassLoader传递给它作为父类加载器,这里还把AppClassLoader默认设置为线程上下文类加载器,关于线程上下文类加载器稍后会分析。那ExtClassLoader类加载器为什么是null呢?看下面的源码创建过程就明白,在创建ExtClassLoader强制设置了其父加载器为null。

    //Lancher中创建ExtClassLoader
    extcl = ExtClassLoader.getExtClassLoader();
    
    //getExtClassLoader()方法
    public static ExtClassLoader getExtClassLoader() throws IOException{
    
      //........省略其他代码 
      return new ExtClassLoader(dirs);                     
      // .........
    }
    
    //构造方法
    public ExtClassLoader(File[] dirs) throws IOException {
       //调用父类构造URLClassLoader传递null作为parent
       super(getExtURLs(dirs), null, factory);
    }
    
    //URLClassLoader构造
    public URLClassLoader(URL[] urls, ClassLoader parent,
                              URLStreamHandlerFactory factory) {
    

    显然ExtClassLoader的父类为null,而AppClassLoader的父加载器为ExtClassLoader,所有自定义的类加载器其父加载器只会是AppClassLoader,注意这里所指的父类并不是Java继承关系中的那种父子关系。

    #类与类加载器
    ##类与类加载器
    在JVM中表示两个class对象是否为同一个类对象存在两个必要条件

    • 类的完整类名必须一致,包括包名。
    • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。

    也就是说,在JVM中,即使这个两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的,这是因为不同的ClassLoader实例对象都拥有不同的独立的类名称空间,所以加载的class对象也会存在不同的类名空间中,但前提是覆写loadclass方法,从前面双亲委派模式对loadClass()方法的源码分析中可以知,在方法第一步会通过Class<?> c = findLoadedClass(name);从缓存查找,类名完整名称相同则不会再次被加载,因此我们必须绕过缓存查询才能重新加载class对象。当然也可直接调用findClass()方法,这样也避免从缓存查找,如下

    String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
    //创建两个不同的自定义类加载器实例
    FileClassLoader loader1 = new FileClassLoader(rootDir);
    FileClassLoader loader2 = new FileClassLoader(rootDir);
    //通过findClass创建类的Class对象
    Class<?> object1=loader1.findClass("com.zejian.classloader.DemoObj");
    Class<?> object2=loader2.findClass("com.zejian.classloader.DemoObj");
    
    System.out.println("findClass->obj1:"+object1.hashCode());
    System.out.println("findClass->obj2:"+object2.hashCode());
    
    /**
      * 直接调用findClass方法输出结果:
      * findClass->obj1:723074861
        findClass->obj2:895328852
        生成不同的实例
      */
    

    如果调用父类的loadClass方法,结果如下,除非重写loadClass()方法去掉缓存查找步骤,不过现在一般都不建议重写loadClass()方法。

    //直接调用父类的loadClass()方法
    Class<?> obj1 =loader1.loadClass("com.zejian.classloader.DemoObj");
    Class<?> obj2 =loader2.loadClass("com.zejian.classloader.DemoObj");
    
    //不同实例对象的自定义类加载器
    System.out.println("loadClass->obj1:"+obj1.hashCode());
    System.out.println("loadClass->obj2:"+obj2.hashCode());
    //系统类加载器
    System.out.println("Class->obj3:"+DemoObj.class.hashCode());
    
    /**
    * 直接调用loadClass方法的输出结果,注意并没有重写loadClass方法
    * loadClass->obj1:1872034366
      loadClass->obj2:1872034366
      Class->    obj3:1872034366
      都是同一个实例
    */
    

    所以如果不从缓存查询相同完全类名的class对象,那么只有ClassLoader的实例对象不同,同一字节码文件创建的class对象自然也不会相同。
    ##了解class文件的显示加载与隐式加载的概念
    所谓class文件的显示加载与隐式加载的方式是指JVM加载class文件到内存的方式,显示加载指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)this.getClass().getClassLoader().loadClass()加载class对象。而隐式加载则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。在日常开发以上两种方式一般会混合使用,这里我们知道有这么回事即可。
    #编写自己的类加载器
    通过前面的分析可知,实现自定义类加载器需要继承ClassLoader或者URLClassLoader,继承ClassLoader则需要自己重写findClass()方法并编写加载逻辑,继承URLClassLoader则可以省去编写findClass()方法以及class文件加载转换成字节码流的代码。那么编写自定义类加载器的意义何在呢?

    • 当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。

    • 当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。

    • 当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。

    ##自定义File类加载器
    这里我们继承ClassLoader实现自定义的特定路径下的文件类加载器并加载编译后DemoObj.class,源码代码如下

    public class DemoObj {
        @Override
        public String toString() {
            return "I am DemoObj";
        }
    }
    
    package com.zejian.classloader;
    
    import java.io.*;
    
    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class FileClassLoader extends ClassLoader {
        private String rootDir;
    
        public FileClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
    
        /**
         * 编写findClass方法的逻辑
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 获取类的class文件字节数组
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                //直接生成class对象
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        /**
         * 编写获取class文件并转换为字节码流的逻辑
         * @param className
         * @return
         */
        private byte[] getClassData(String className) {
            // 读取类文件的字节
            String path = classNameToPath(className);
            try {
                InputStream ins = new FileInputStream(path);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                // 读取类文件的字节码
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 类文件的完全路径
         * @param className
         * @return
         */
        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }
    
        public static void main(String[] args) throws ClassNotFoundException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            FileClassLoader loader = new FileClassLoader(rootDir);
    
            try {
                //加载指定的class文件
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                System.out.println(object1.newInstance().toString());
              
                //输出结果:I am DemoObj
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    显然我们通过getClassData()方法找到class文件并转换为字节流,并重写findClass()方法,利用defineClass()方法创建了类的class对象。在main方法中调用了loadClass()方法加载指定路径下的class文件,由于启动类加载器、拓展类加载器以及系统类加载器都无法在其路径下找到该类,因此最终将有自定义类加载器加载,即调用findClass()方法进行加载。如果继承URLClassLoader实现,那代码就更简洁了,如下:

    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class FileUrlClassLoader extends URLClassLoader {
    
        public FileUrlClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }
    
        public FileUrlClassLoader(URL[] urls) {
            super(urls);
        }
    
        public FileUrlClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
            super(urls, parent, factory);
        }
    
    
        public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            File file = new File(rootDir);
            //File to URI
            URI uri=file.toURI();
            URL[] urls={uri.toURL()};
    
            FileUrlClassLoader loader = new FileUrlClassLoader(urls);
    
            try {
                //加载指定的class文件
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                System.out.println(object1.newInstance().toString());
              
                //输出结果:I am DemoObj
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    非常简洁除了需要重写构造器外无需编写findClass()方法及其class文件的字节流转换逻辑。
    ##自定义网络类加载器
    自定义网络类加载器,主要用于读取通过网络传递的class文件(在这里我们省略class文件的解密过程),并将其转换成字节流生成对应的class对象,如下

    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class NetClassLoader extends ClassLoader {
    
        private String url;//class文件的URL
    
        public NetClassLoader(String url) {
            this.url = url;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = getClassDataFromNet(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        /**
         * 从网络获取class文件
         * @param className
         * @return
         */
        private byte[] getClassDataFromNet(String className) {
            String path = classNameToPath(className);
            try {
                URL url = new URL(path);
                InputStream ins = url.openStream();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                // 读取类文件的字节
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                //这里省略解密的过程.......
                return baos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private String classNameToPath(String className) {
            // 得到类文件的URL
            return url + "/" + className.replace('.', '/') + ".class";
        }
    
    }
    

    比较简单,主要是在获取字节码流时的区别,从网络直接获取到字节流再转车字节数组然后利用defineClass方法创建class对象,如果继承URLClassLoader类则和前面文件路径的实现是类似的,无需担心路径是filePath还是Url,因为URLClassLoader内的URLClassPath对象会根据传递过来的URL数组中的路径判断是文件还是jar包,然后根据不同的路径创建FileLoader或者JarLoader或默认类Loader去读取对于的路径或者url下的class文件。
    ##热部署类加载器
    所谓的热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象(关于这点的原因前面已分析过,即利用不同的类加载实例),由于JVM在加载类之前会检测请求的类是否已加载过(即在loadClass()方法中调用findLoadedClass()方法),如果被加载过,则直接从缓存获取,不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次,多次加载将报错,因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,以实现所谓的热部署。实际上前面的实现的FileClassLoader和FileUrlClassLoader已具备这个功能,但前提是直接调用findClass()方法,而不是调用loadClass()方法,因为ClassLoader中loadClass()方法体中调用findLoadedClass()方法进行了检测是否已被加载,因此我们直接调用findClass()方法就可以绕过这个问题,当然也可以重新loadClass方法,但强烈不建议这么干。利用FileClassLoader类测试代码如下:

     public static void main(String[] args) throws ClassNotFoundException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            FileClassLoader loader = new FileClassLoader(rootDir);
            FileClassLoader loader2 = new FileClassLoader(rootDir);
    
            try {
                //加载指定的class文件,调用loadClass()
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                Class<?> object2=loader2.loadClass("com.zejian.classloader.DemoObj");
    
                System.out.println("loadClass->obj1:"+object1.hashCode());
                System.out.println("loadClass->obj2:"+object2.hashCode());
    
                //加载指定的class文件,直接调用findClass(),绕过检测机制,创建不同class对象。
                Class<?> object3=loader.findClass("com.zejian.classloader.DemoObj");
                Class<?> object4=loader2.findClass("com.zejian.classloader.DemoObj");
    
                System.out.println("loadClass->obj3:"+object3.hashCode());
                System.out.println("loadClass->obj4:"+object4.hashCode());
    
                /**
                 * 输出结果:
                 * loadClass->obj1:644117698
                   loadClass->obj2:644117698
                   findClass->obj3:723074861
                   findClass->obj4:895328852
                 */
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    #双亲委派模型的破坏者-线程上下文类加载器

        在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。
        线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

    从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。为了进一步证实这种场景,不妨看看DriverManager类的源码,DriverManager是Java核心rt.jar包中的类,该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver,这里主要看看如何加载外部实现类,在DriverManager初始化时会执行如下代码

    //DriverManager是Java核心包rt.jar的类
    public class DriverManager {
    	//省略不必要的代码
        static {
            loadInitialDrivers();//执行该方法
            println("JDBC DriverManager initialized");
        }
    
    //loadInitialDrivers方法
     private static void loadInitialDrivers() {
         sun.misc.Providers()
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
    				//加载外部的Driver的实现类
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                  //省略不必要的代码......
                }
            });
        }
    

    在DriverManager类初始化时执行了loadInitialDrivers()方法,在该方法中通过ServiceLoader.load(Driver.class);去加载外部实现的驱动类,ServiceLoader类会去读取mysql的jdbc.jar下META-INF文件的内容,如下所示

    而com.mysql.jdbc.Driver继承类如下:

    public class Driver extends com.mysql.cj.jdbc.Driver {
        public Driver() throws SQLException {
            super();
        }
    
        static {
            System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "
                    + "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
        }
    }
    

    从注释可以看出平常我们使用com.mysql.jdbc.Driver已被丢弃了,取而代之的是com.mysql.cj.jdbc.Driver,也就是说官方不再建议我们使用如下代码注册mysql驱动

    //不建议使用该方式注册驱动类
    Class.forName("com.mysql.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
          
    

    而是直接去掉注册步骤,如下即可

    String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
          
    

    这样ServiceLoader会帮助我们处理一切,并最终通过load()方法加载,看看load()方法实现

    public static <S> ServiceLoader<S> load(Class<S> service) {
    	 //通过线程上下文类加载器加载
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          return ServiceLoader.load(service, cl);
      }
    

    很明显了确实通过线程上下文类加载器加载的,实际上核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的,通过这种方式实现了Java核心代码内部去调用外部实现类。我们知道线程上下文类加载器默认情况下就是AppClassLoader,那为什么不直接通过getSystemClassLoader()获取类加载器来加载classpath路径下的类的呢?其实是可行的,但这种直接使用getSystemClassLoader()方法获取AppClassLoader加载类有一个缺点,那就是代码部署到不同服务时会出现问题,如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题,因为这些服务使用的线程上下文类加载器并非AppClassLoader,而是Java Web应用服自家的类加载器,类加载器不同。,所以我们应用该少用getSystemClassLoader()。总之不同的服务使用的可能默认ClassLoader是不同的,但使用线程上下文类加载器总能获取到与当前程序执行相同的ClassLoader,从而避免不必要的问题。ok~.关于线程上下文类加载器暂且聊到这,前面阐述的DriverManager类,大家可以自行看看源码,相信会有更多的体会,另外关于ServiceLoader本篇并没有过多的阐述,毕竟我们主题是类加载器,但ServiceLoader是个很不错的解耦机制,大家可以自行查阅其相关用法。

    ok~,本篇到此告一段落,如有误处,欢迎留言,谢谢。

    参考资料:
    http://blog.csdn.net/yangcheng33/article/details/52631940
    http://ifeve.com/wp-content/uploads/2014/03/JSR133中文版1.pdf

    《深入理解JVM虚拟机》
    《深入分析Java Web 技术内幕》

    展开全文
  • 自定义classloader的使用
  • 一看你就懂,超详细java中的ClassLoader详解

    万次阅读 多人点赞 2017-02-10 19:26:54
    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载...

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。

    备注:本文篇幅比较长,但内容简单,大家不要恐慌,安静地耐心翻阅就是

    Class文件的认识

    我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。如我们编写一个简单的程序HelloWorld.java

    public class HelloWorld{
       
    
    	public static void 
    展开全文
  • ClassLoader详解

    2019-08-27 20:46:04
    1.Java中的ClassLoader 1.1 CLassLoader的类型 Java的类加载器主要有两种类型,即系统类加载器和自定义类加载器。系统类加载器包括3中,分别是Bootstrap ClassLoader,Extensions ClassLoader和Application ...
  • 破解java加密的rt.jar,在classloader植入破解代码,默认输出到c:/TEMP/classes/目录。使用方法:只要下载本rt.jar,然后替换掉jdk1.8.0_25\jre\lib目录下的rt.jar。然后运行你需要破解的java程序即可,如果你的java...
  • ClassLoader动态加载dex

    2018-12-17 18:13:23
    通过对ClassLoader中两个子类加载dex,来熟悉安卓中的apk加载流程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 202,561
精华内容 81,024
关键字:

classloader