类的加载过程

news2025/1/16 12:47:56

一、前言
  类加载器的技术
二、类的加载过程
2.1 JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)。链接又分为三个步骤,如下图所示:
在这里插入图片描述

  1. 装载:查找并加载类的二进制数据

  2. 链接:

验证:确保被加载类的正确性;

准备:为类的静态变量分配内存,并将其初始化为默认值;

解析:把类中的符号引用转换为直接引用;

  1. 初始化:为类的静态变量赋予正确的初始值

    那为什么要有验证这一步骤呢?首先如果由编译器生成的class文件,它肯定是符合JVM字节码格式的,但是万一有高手自己写一个class文件,让JVM加载并运行,用于恶意用途,就不妙了,因此这个class文件要先过验证这一关,不符合的话不会让它继续执行的,也是为了安全考虑吧。

    准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

2.2 类的初始化

类什么时候才被初始化:

1)创建类的实例,也就是new一个对象

2)访问某个类或接口的静态变量,或者对该静态变量赋值

3)调用类的静态方法

4)反射(Class.forName(“com.lyj.load”))

5)初始化一个类的子类(会首先初始化子类的父类)

6)JVM启动时标明的启动类,即文件名和类名相同的那个类

只有这6中情况才会导致类的类的初始化。类的初始化步骤:

    1)如果这个类还没有被加载和链接,那先进行加载和链接

    2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)

    3) 假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

2.3 类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。看下面2图
在这里插入图片描述

在这里插入图片描述

类的加载的最终产品是位于堆区中的Class对象。
Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口

加载类的方式有以下几种:

1)从本地系统直接加载

2)通过网络下载.class文件

3)从zip,jar等归档文件中加载.class文件

4)从专有数据库中提取.class文件

5)将Java源文件动态编译为.class文件(服务器)

2.4 加载器

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

1)Bootstrap ClassLoader

负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

2)Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目录下的jar包

3)App ClassLoader

负责加载classpath中指定的jar包及目录中class

4)Custom ClassLoader

属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类在所有ClassLoader只加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

三、 JVM三种预定义类加载器
    JVM预定义有三种类加载器,当一个 JVM启动的时候,Java缺省开始使用如下三种类加载器:

1)引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。它负责将<Java_Runtime_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

2)扩展类加载器(extensions class loader):该类加载器在此目录里面查找并加载 Java 类。扩展类加载器是由Sun的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dirs指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器

3)系统类加载器(system class loader):系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它

四、 类加载器“双亲委派”机制
4.1 “双亲委派”机制介绍

在这里,需要着重说明的是,JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。

在这里插入图片描述

图一 标准扩展类加载器继承层次图

在这里插入图片描述

图二 系统类加载器继承层次图

通过图一和图二我们可以看出,类加载器均是继承自java.lang.ClassLoader抽象类。我们下面我们就看简要介绍一下java.lang.ClassLoader中几个最重要的方法:

//加载指定名称(包括包名)的二进制类型,供用户调用的接口  
public Class<?> loadClass(String name) throws ClassNotFoundException{}  
  
//加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是这里的resolve参数不一定真正能达到解析的效果),供继承用  
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{}  
  
//findClass方法一般被loadClass方法调用去加载指定名称类,供继承用  
protected Class<?> findClass(String name) throws ClassNotFoundException {}  
  
//定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承  
//(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)  
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}  

通过进一步分析标准扩展类加载器(sun.misc.Launcher E x t C l a s s L o a d e r )和系统类加载器( s u n . m i s c . L a u n c h e r ExtClassLoader)和系统类加载器(sun.misc.Launcher ExtClassLoader)和系统类加载器(sun.misc.LauncherAppClassLoader)的代码以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)的代码可以看出,都没有覆写java.lang.ClassLoader中默认的加载委派规则—loadClass(…)方法。既然这样,我们就可以通过分析java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟机默认采用的双亲委派机制到底是什么模样:

public Class<?> loadClass(String name) throws ClassNotFoundException {
  return loadClass(name, false);
}

protected synchronized Class<?> loadClass(String name, boolean resolve)  throws ClassNotFoundException {
   // 首先判断该类型是否已经被加载
   Class c = findLoadedClass(name);
   if (c == null) {
     //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
       try {
         if (parent != null) {
         //如果存在父类加载器,就委派给父类加载器加载
             c = parent.loadClass(name, false);
           } else {
        //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
             c = findBootstrapClass0(name);
           }
       }catch (ClassNotFoundException e) {
         // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
         c = findClass(name);
       }
   }
   if (resolve) {
       resolveClass(c);
   }
   return c;
}

通过上面的代码分析,我们可以对JVM采用的双亲委派类加载机制有了更感性的认识,下面我们就接着分析一下启动类加载器、标准扩展类加载器和系统类加载器三者之间的关系。可能大家已经从各种资料上面看到了如下类似的一幅图片:

在这里插入图片描述

图三 类加载器默认委派关系图

上面图片给人的直观印象是系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器,下面我们就用代码具体测试一下:

public static void main(String[] args) {
     try {      System.out.println(ClassLoader.getSystemClassLoader());
      System.out.println(ClassLoader.getSystemClassLoader().getParent();
      System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
     } catch (Exception e) {
         e.printStackTrace();
     }
  }

(说明:通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器)

代码输出如下:

sun.misc.Launcher$AppClassLoader@197d257
sun.misc.Launcher$ExtClassLoader@7259da
null

通过以上的代码输出,我们可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,就是说标准扩展类加载器本身强制设定父类加载器为null。我们还是借助于代码分析一下:


我们首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数:


   protected ClassLoader() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        //默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器
        this.parent = getSystemClassLoader();
        initialized = true;
    }
    protected ClassLoader(ClassLoader parent) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        //强制设置父类加载器
        this.parent = parent;
        initialized = true;
    }

    再看一下ClassLoader抽象类中parent成员的声明:
    // The parent class loader for delegation
    private ClassLoader parent;

声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的setter方法),结合前面的测试代码的输出,我们可以推断出:

1.系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认 会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)

2.扩展类加载器(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为null。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)

现在我们可能会有这样的疑问:扩展类加载器(ExtClassLoader)的父类加载器被强制设置为null了,那么扩展类加载器为什么还能将加载任务委派给启动类加载器呢?
在这里插入图片描述

图四 标准扩展类加载器和系统类加载器成员大纲视图

在这里插入图片描述

图五 扩展类加载器和系统类加载器公共父类成员大纲视图

通过图四和图五可以看出,标准扩展类加载器和系统类加载器及其父类(java.net.URLClassLoader和java.security.SecureClassLoader)都没有覆写java.lang.ClassLoader中默认的加载委派规则—loadClass(…)方法。有关java.lang.ClassLoader中默认的加载委派规则前面已经分析过,如果父加载器为null,则会调用本地方法进行启动类加载尝试。所以,图三中,启动类加载器、标准扩展类加载器和系统类加载器之间的委派关系事实上是仍就成立的。(在后面的用户自定义类加载器部分,还会做更深入的分析)。

4.2 类加载双亲委派示例

以上已经简要介绍了虚拟机默认使用的启动类加载器、标准扩展类加载器和系统类加载器,并以三者为例结合JDK代码对JVM默认使用的双亲委派类加载机制做了分析。下面我们就来看一个综合的例子。首先在eclipse中建立一个简单的java应用工程,然后写一个简单的JavaBean如下:

 package classloader.test.bean;
    public class TestBean {
        public TestBean() {}
    }

在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容如下:

测试一:
 public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            //查看当前系统类路径中包含的路径条目
            System.out.println(System.getProperty("java.class.path"));
            //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean
            Class typeLoaded = Class.forName("classloader.test.bean.TestBean");
            //查看被加载的TestBean类型是被那个类加载器加载的
            System.out.println(typeLoaded.getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 }
对应的输出如下:
    D:"DEMO"dev"Study"ClassLoaderTest"bin
    sun.misc.Launcher$AppClassLoader@197d257
(说明:当前类路径默认的含有的一个条目就是工程的输出目录)

测试二:
  将当前工程输出目录下的…/classloader/test/bean/TestBean.class打包进test.jar剪贴到< Java_Runtime_Home >/lib/ext目录下(现在工程输出目录下和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果如下:
  D:"DEMO"dev"Study"ClassLoaderTest"bin
  sun.misc.Launcher$ExtClassLoader@7259da
  对比测试一和测试二,我们明显可以验证前面说的双亲委派机制,系统类加载器在接到加载classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。

测试三:
  将test.jar拷贝一份到< Java_Runtime_Home >/lib下,运行测试代码,输出如下:
  D:"DEMO"dev"Study"ClassLoaderTest"bin
  sun.misc.Launcher$ExtClassLoader@7259da

测试三和测试二输出结果一致。那就是说,放置到放置到
  < Java_Runtime_Home >/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。做个进一步验证,删除< Java_Runtime_Home >/lib/ext目录下和工程输出目录下的TestBean对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。有关这个问题,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中设置相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass方法中被加载,当前运行的类加载器正是扩展类加载器(sun.misc.Launcher$ExtClassLoader),这一点可以通过JDT中变量视图查看验证。

五、java程序动态扩展方式
  Java的连接模型允许用户运行时扩展引用程序,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,又允许用户自行定义类装载器,在运行时动态扩展用户的程序。通过用户自定义的类装载器,你的程序可以装载在编译时并不知道或者尚未存在的类或者接口,并动态连接它们并进行有选择的解析。

运行时动态扩展java应用程序有如下两个途径:

5.1 调用java.lang.Class.forName(…)

这个方法其实在前面已经讨论过,在后面的问题2解答中说明了该方法调用会触发哪个类加载器开始加载任务。这里需要说明的是多参数版本的forName(…)方法:

public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

这里的initialize参数是很重要的,可以决定类被加载同时是否完成初始化的工作(说明: 单参数版本的forName方法默认是不完成初始化的).有些场景下,需要将initialize设置为true来强制加载同时完成初始化,例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题,因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用,这就要求驱动程序类必须被初始化,而不单单被加载.

5.2 用户自定义类加载器

通过前面的分析,我们可以看出,除了和本地实现密切相关的启动类加载器之外,包括标准扩展类加载器和系统类加载器在内的所有其他类加载器我们都可以当做自定义类加载器来对待,唯一区别是是否被虚拟机默认使用。前面的内容中已经对java.lang.ClassLoader抽象类中的几个重要的方法做了介绍,这里就简要叙述一下一般用户自定义类加载器的工作流程吧(可以结合后面问题解答一起看):

1)首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2

2)委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,整个虚拟机中各种类加载器最终会呈现树状结构),如果父类加载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3

3)调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,终止加载过程(注意:这里的异常种类不止一种)。

(说明:这里说的自定义类加载器是指JDK 1.2以后版本的写法,即不覆写改变java.lang.loadClass(…)已有委派逻辑情况下)

六、常见问题分析
  6.1 由不同的类加载器加载的指定类型还是相同的类型吗?

在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间。我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。

6.2 在代码中直接调用Class.forName(String name)方法,到底会触发哪个类加载器进行类加载行为?
  Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:

//java.lang.Class.java
  public static Class<?> forName(String className)throws ClassNotFoundException {
   return forName0(className, true, ClassLoader.getCallerClassLoader());
 }
 //java.lang.ClassLoader.java
 // Returns the invoker's class loader, or null if none.
 static ClassLoader getCallerClassLoader() {
    // 获取调用类(caller)的类型
    Class caller = Reflection.getCallerClass(3);
    // This can be null if the VM is  requesting it
    if (caller == null) {
       return null;
    }
    // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader
    return caller.getClassLoader0();
 }
 //java.lang.Class.java
 //虚拟机本地实现,获取当前类的类加载器,前面介绍的Class的getClassLoader()也使用此方法
 native ClassLoader getClassLoader0();

6.3 在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?

前面讲过,在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:

//摘自java.lang.ClassLoader.java
protected ClassLoader() {
  SecurityManager security = System.getSecurityManager();
  if (security != null) {
    security.checkCreateClassLoader();
    }
  this.parent = getSystemClassLoader();
  initialized = true;
}

我们再来看一下对应的getSystemClassLoader()方法的实现:

private static synchronized void initSystemClassLoader() {
  //...
  sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
  scl = l.getClassLoader();
  //...
}

我们可以写简单的测试代码来测试一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());
  本机对应输出如下:

sun.misc.Launcher$AppClassLoader@197d257
  所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:

即使用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类:

1. <Java_Runtime_Home>/lib下的类

2. < Java_Runtime_Home >/lib/ext下或者由系统变量java.ext.dir指定位置中的类

3. 当前工程类路径下或者由系统变量java.class.path指定位置中的类

6.4 在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗?

JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论:

即使用户自定义类加载器不指定父类加载器,那么,同样可以加载到<Java_Runtime_Home>/lib下的类,但此时就不能够加载<Java_Runtime_Home>/lib/ext目录下的类了。

说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。

6.5 编写自定义类加载器时,一般有哪些注意点?

  1. 一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑

一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题:

//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑)
public class WrongClassLoader extends ClassLoader {
  public Class<?> loadClass(String name) throws ClassNotFoundException {
     returnthis.findClass(name);
   }
  protected Class<?> findClass(String name) throws ClassNotFoundException {
     //假设此处只是到工程以外的特定目录D:/library下去加载类
     具体实现代码省略
   }
}
通过前面的分析我们已经知道,用户自定义类加载器(WrongClassLoader)的默认的类加载器是系统类加载器,但是现在问题4中的结论就不成立了。大家可以简单测试一下,现在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工程类路径上的类都加载不上了。
//问题5 测试代码一
public class WrongClassLoaderTest {
        public static void main(String[] args) {
           try {
               WrongClassLoader loader = new WrongClassLoader();
               Class classLoaded = loader.loadClass("beans.Account");
               System.out.println(classLoaded.getName());
               System.out.println(classLoaded.getClassLoader());
           } catch (Exception e) {
               e.printStackTrace();
           }
        }
}
//说明:D:"classes"beans"Account.class物理存在的)
//输出结果:
java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:106)
    at WrongClassLoader.findClass(WrongClassLoader.java:40)
    at WrongClassLoader.loadClass(WrongClassLoader.java:29)
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:400)
    at WrongClassLoader.findClass(WrongClassLoader.java:43)
    at WrongClassLoader.loadClass(WrongClassLoader.java:29)
    at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:400)
    at WrongClassLoader.findClass(WrongClassLoader.java:43)
    at WrongClassLoader.loadClass(WrongClassLoader.java:29)
    at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
复制代码
  这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass(…)引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。
//问题5 测试二
//用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑)
public class WrongClassLoader extends ClassLoader {
   protected Class<?> findClass(String name) throws ClassNotFoundException {
   //假设此处只是到工程以外的特定目录D:/library下去加载类
   //具体实现代码省略
    }
}
 //将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下:
  beans.Account
  WrongClassLoader@1c78e57

这说明,beans.Account加载成功,且是由自定义类加载器WrongClassLoader加载。这其中的原因分析,我想这里就不必解释了,大家应该可以分析的出来了。

2) 正确设置父类加载器

通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。

3) 保证findClass(String )方法的逻辑正确性

事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。

6.6 如何在运行时判断系统类加载器能加载哪些路径下的类?

一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;

二是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty(“java.class.path”)

6.7 如何在运行时判断标准扩展类加载器能加载哪些路径下的类?

方法之一:
try {
  URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();
  for (int i = 0; i < extURLs.length; i++) {
    System.out.println(extURLs[i]);
   }
} catch (Exception e) {//…}
//本机对应输出如下:
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/557421.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux命名管道总结

FIFO&#xff0c;也称为命名管道&#xff0c;它是一种文件类型。 1、特点 (1)FIFO可以在无关的进程之间交换数据&#xff0c;与无名管道不同。 (2)FIFO有路径名与之相关联&#xff0c;它以一种特殊设备文件形式存在于文件系统中。 2、原型 (1)#include <sys/types.h> #in…

一篇文章告诉你什么是Java内存模型

在上篇 并发编程Bug起源:可见性、有序性和原子性问题&#xff0c;介绍了操作系统为了提示运行速度&#xff0c;做了各种优化&#xff0c;同时也带来数据的并发问题&#xff0c; 定义 在单线程系统中&#xff0c;代码按照顺序从上往下顺序执行&#xff0c;执行不会出现问题。比…

一图看懂 click 模块:一个通过组合的方式来创建精美命令行界面的包,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 click 模块&#xff1a;一个通过组合的方式来创建精美命令行界面的包&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#…

Python篇——数据结构与算法(第一部分)

目录 一、查找 1、顺序查找&#xff1a;也叫线性查找&#xff0c;从列表第一个元素开始&#xff0c;顺序进行搜索&#xff0c;直到找到元素或搜索到列表最后一个元素为止。 2、二分查找&#xff1a;也叫折半查找&#xff0c;从有序列表的初始候选区li[0:n]开始&#xff0c;通…

【远程访问】Linux搭建SVN服务器,并内网穿透实现公网远程访问

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

C++入门预备语法

C入门预备语法 C关键字命名空间C输入&输出初步缺省参数函数重载引用内联函数auto和范围for&#xff08;C11&#xff09;指针空值nullptr C关键字 命名空间 命名空间是一种将变量名、函数名、类名和库名称等封装到一个命名空间域中&#xff0c;与其他域的同名量相隔离&…

【AUTOSAR】【以太网】SomeIpTp

目录 一、概述 二、限制与约束 三、功能说明 3.1 SOME/IP帧头 3.1.1 消息类型字段 3.1.2 偏移字段 3.1.3 更多段标志 3.1.4 示例 3.2 错误分类 3.2.1 开发错误 3.2.2 运行错误 四、API接口 4.1 API定义 4.2 回调接口 4.3 调度接口 一、概述 规范规定了AUTOSAR 基…

知识付费:创客匠人的发展转型之路

互联网时代到来后&#xff0c;知识付费行业以极快的速度崛起&#xff0c;让最早入局的人赚得盆满钵满&#xff0c;同时&#xff0c;也有很多人想进入行业发展&#xff0c;却没有真正打造好自己的平台&#xff0c;无法形成系统成熟的企业。如今&#xff0c;行业发展趋势还在不断…

案例19:Java私房菜定制上门服务系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

小型双轮差速底盘实现红外避障功能

1. 功能说明 在R023e机器人车体上安装1个近红外传感器&#xff0c;实现机器人小车避障功能。 2. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&#xff08;兼容Arduino Uno&#xff09; 扩展板 Bigfish2.1扩展板…

VS2022 CUDA环境配置

文章目录 安装准备新建项目 安装准备 配置Cuda环境主要分为以下几个步骤 安装VS 这个应该不用太说&#xff0c;直接装最新版安装CUDA 下载地址&#xff1a;Cuda Toolkit安装cuDNN 下载地址&#xff1a;cuDNN archieve 这个安装顺序非常重要&#xff0c;一定是先装VS后装CUDA…

19 # promisify:将回调方法 promise 化

之前写个单独的方法去处理文件读取 function read(filename) {return new Promise((resolve, reject) > {fs.readFile(filename, "utf-8", function (err, data) {if (err) reject(err);resolve(data);});}); }将 node 的 api 快速的转化成 promise 的形式 cons…

Linux基于Apache服务搭建简易镜像站

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Linux基于Apache服务搭建简易镜像站 安装Apache服务器 yum install -y httpd.x86_64 配置Apache服务器&#xff1a;编辑Apache配置文件/etc/httpd/conf/httpd.conf #S…

深度学习 - 50.推荐场景下的 Attention And Multi-Head Attention

目录 一.引言 二.Attention 1.Common Attention 2.Google Attention 三.Multi-Head Attention 四.总结 一.引言 Attention 注意力机制最早来源于我们自身的视觉感官&#xff0c;当我们视觉获取到图像信息时&#xff0c;我们并不是从前往后从上往下均匀的扫描画面&#x…

如何用FinalShell连接VirtualBoxLinux虚拟机?

本章教程&#xff0c;主要介绍一下如何用FinalShell连接VirtualBoxLinux虚拟机。 当安装完虚拟机的时候&#xff0c;你可能会遇到找不到ifconfig命令。这个是因为当时安装过程中&#xff0c;我们选择的是最小化安装&#xff0c;所以有些命令是需要按需进行安装的。 目录 1、li…

JavaScript:原型、原型链、继承

一、理解原型 1.1、人工智能解释 JavaScript中的原型是一种机制&#xff0c;它允许在创建对象时共享属性和方法。每个JavaScript对象都有一个原型对象&#xff0c;它包含一些公共属性和方法&#xff0c;可以被该对象和其他对象共享。 当你创建一个对象时&#xff0c;Javascr…

案例13:Java社区帮扶对象管理系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

HTML+CSS实训——Day01——安装好环境+写一个简单的应用启动页面

前言 学校最近开始实训周了&#xff0c;一上就是一个月&#xff0c;本来想在课上学点考研的东西的&#xff0c;但是无奈任务重&#xff0c;而且最后还能有点小奖励&#xff0c;就认真学了&#xff0c;再者说&#xff0c;html也挺重要的&#xff0c;学一学也不算浪费时间。 软…

【JUC】Synchronized与锁升级

【JUC】Synchronized与锁升级 文章目录 【JUC】Synchronized与锁升级1. 概述1.1 无锁1.2 偏向锁 1. 概述 用锁能够实现数据的安全性&#xff0c;但是会带来性能下降。无锁能够基于线程并行提升程序性能&#xff0c;但是会带来安全性下降。如何达到两者的平衡呢&#xff1f; s…

VuePress V1 踩坑记录

文章目录 前言1.Node.js 版本问题2.侧边栏3.添加页面目录导航4.非首页 footer 不生效5.部署到 Github 的错误vuepress 的 docs 与 Github Pages 的 docs 目录冲突样式丢失 7.资源引用问题本地图片找不到引用 CSDN 图片报 403 错误 参考文献 前言 我的第二本开源电子书《后台开…