【JVM】加载机制

一.类装载子系统

介绍

1、类加载子系统负责从文件系统或是网络中加载.class文件,class文件在文件开头有特定的文件标识。 2、把加载后的class类信息存放于方法区,除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射); 3、ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定; 4、如果调用构造器实例化对象,则该对象存放在堆区。

类加载器ClassLoader角色

  1. class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
  2. class file 加载到JVM中,被称为DNA元数据模板。
  3. 在 .class文件 --> JVM --> 最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一个快递员的角色。

类加载的执行过程

类使用的7个阶段
类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸载(Unloading)这7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)。

加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段不一定:它在某些情况下可以初始化阶段之后在开始,这是为了支持Java语言的运行时绑定(也称为动态绑定)。接下来讲解加载、验证、准备、解析、初始化五个步骤,这五个步骤组成了一个完整的类加载过程。使用没什么好说的,卸载属于GC的工作 。
发生顺序

加载

加载是类加载的第一个阶段。有两种时机会触发类加载:
① 预加载
虚拟机启动时加载,加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面的内容是程序运行时非常常用到的,像java.lang.*、java.util.、 java.io. 等等,因此随着虚拟机一起加载。要证明这一点很简单,写一个空的main函数,设置虚拟机参数为"-XX:+TraceClassLoading"来获取类加载信息。
②运行时加载
虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。那么,加载阶段做了什么,其实加载阶段做了有三件事情:

  • 获取.class文件的二进制流
  • 将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中
  • 在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

虚拟机规范对这三点的要求并不具体,因此虚拟机实现与具体应用的灵活度都是相当大的。例如第一条,根本没有指明二进制字节流要从哪里来、怎么来,因此单单就这一条,就能变出许多花样来:

  • 从zip包中获取,这就是以后jar、ear、war格式的基础
  • 从网络中获取,典型应用就是Applet
  • 运行时计算生成,典型应用就是动态代理技术
  • 由其他文件生成,典型应用就是JSP,即由JSP生成对应的.class文件
  • 从数据库中读取,这种场景比较少见

总而言之,在类加载整个过程中,这部分是对于开发者来说可控性最强的一个阶段。

链接

链接包含三个步骤: 分别是 验证(Verification) , 准备(Preparation), 解析(Resolution) 三个过程
①验证(Verification)
  连接阶段的第一步,这一阶段的目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  Java语言本身是相对安全的语言(相对C/C++来说),但是前面说过,.class文件未必要从Java源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编写来产生.class文件。在字节码语言层面上,Java代码至少从语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。

  验证阶段将做一下几个工作,具体就不细讲了,这是虚拟机实现层面的问题:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

②准备(Preparation)
准备阶段是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配。关于这点,有两个地方注意一下:

  • 这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中
  • 这个阶段赋初始值的变量指的是那些不被final修饰的static变量,比如"public static int value = 123",value在准备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如"public static final int value =123;"就不一样了,在准备阶段,虚拟机就会给value赋值为123。
 *注意:*
这是因为局部变量不像类变量那样存在准备阶段。
类变量有两次赋初始值的过程,一次在准备阶段,赋予初始值(也可以是指定值);另外一次在初始化阶段,赋予程序员定义的值。
因此,即使程序员没有为类变量赋值也没有关系,它仍然有一个默认的初始值。
但局部变量就不一样了,如果没有给它赋初始值,是不能使用的。

③解析Resolution
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,负责把整个类激活,串成一个可以找到彼此的网。大体分为类或接口的解析、类方法解析、接口方法、解析字段解析

符号引用和直接引用有什么区别:
1、符号引用
符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
这个其实是属于编译原理方面的概念,符号引用包括了下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

符号引用和虚拟机的内存布局是没有关系的,引用的目标未必已经加载到内存中了。
2、直接引用
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机示例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经存在在内存中了。

初始化

类的初始化阶段是类加载过程的最后一个步骤, 之前介绍的几个类加载的动作里, 除了在加载阶 段用户应用程序可以通过自定义类加载器的方式局部参与外, 其余动作都完全由Java虚拟机来主导控 制。 直到初始化阶段, Java虚拟机才真正开始执行类中编写的Java程序代码, 将主导权移交给应用程序。
初始化阶段就是执行类构造器cinit方法的过程。cinit并不是程序员在Java代码中直接编写 的方法, 它是Javac编译器的自动生成物,cinit方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块) 中的 语句合并产生的, 编译器收集的顺序是由语句在源文件中出现的顺序决定的, 静态语句块中只能访问 到定义在静态语句块之前的变量, 定义在它之后的变量, 在前面的静态语句块可以赋值, 但是不能访 问。

public class TestClinit {
	static {	//静态变量和静态代码块的加载顺序由编写先后决定 
		i = 0; // 给变量复制可以正常编译通过
		System.out.print(i); // 这句编译器会提示“非法向前引用”
	}
	static int i = 1;
}

  cinit方法与类的构造函数(即在虚拟机视角中的实例构造器cinit方法) 不同, 它不需要显 式地调用父类构造器, Java虚拟机会保证在子类的cinit方法执行前, 父类的cinit方法已经执行 完毕。 因此在Java虚拟机中第一个被执行的cinit方法的类型肯定是java.lang.Object。
由于父类的cinit方法先执行, 也就意味着父类中定义的静态语句块要优先于子类的变量赋值 操作。
  cinit方法对于类或接口来说并不是必需的, 如果一个类中没有静态语句块, 也没有对变量的 赋值操作, 那么编译器可以不为这个类生成cinit方法。 接口中不能使用静态语句块, 但仍然有变量初始化的赋值操作, 因此接口与类一样都会生成 cinit方法。
  但接口与类不同的是, 执行接口的cinit方法不需要先执行父接口的cinit方法, 因为只有当父接口中定义的变量被使用时, 父接口才会被初始化。 此外, 接口的实现类在初始化时也 一样不会执行接口的cinit方法。
  Java虚拟机必须保证一个类的cinit方法在多线程环境中被正确地加锁同步, 如果多个线程同 时去初始化一个类, 那么只会有其中一个线程去执行这个类的cinit方法, 其他线程都需要阻塞等 待, 直到活动线程执行完毕cinit方法。 如果在一个类的cinit方法中有耗时很长的操作, 那就 可能造成多个进程阻塞, 在实际应用中这种阻塞往往是很隐蔽的法先执行, 也就意味着父类中定义的静态语句块要优先于子类的变量赋值 操作。
类初始化 和 对象初始化
类初始化cinit
对象初始化init

  其中 static 字段和 static 代码块,是属于类的,在类的加载的初始化阶段就已经被执行。类信息会被存放在方法区,在同一个类加载器下,这些信息有一份就够了,所以上面的 static 代码块只会执行一次,它对应的是cinit方法。
在这里插入图片描述

public class ParentA {
  static {
    System.out.print("1");
 }
  public ParentA() {
    System.out.print("2");
 }
}
class SonB extends ParentA {
  static {
    System.out.print("a");
 }
  public SonB() {
    System.out.print("b");
 }
  public static void main(String[] args) {
    ParentA ab = new SonB();
    ab = new SonB();
    //1 a 2 b 2 b 
 
 }
}

二.类加载器

介绍

  类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

  • 注意:JVM主要在程序第一次主动使用类的时候,才会去加载该类,也就是说,JVM并不是在一开始就把一个
    程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。

分类

  1. jvm支持两种类型的加载器,分别是引导类加载器和 自定义加载器
  2. 引导类加载器是由c/c++实现的,自定义加载器是由java实现的。
  3. jvm规范定义自定义加载器是指派生于抽象类ClassLoder的类加载器。
  4. 按照这样的加载器的类型划分,在程序中我们最常见的类加载器是:引导类加载器BootStrapClassLoader、自定
    义类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader)
      

三.双亲委派模型

介绍

  双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。
在这里插入图片描述

为什么需要双亲委派模型?

1、安全;2、节省空间,即防止内存中出现多份同样的字节码。
安全:

		黑客自定义一个 java.lang.String 类,该 String 类具有系统的 String 类一样的功能,只是在某个函数
稍作修改。比如 equals 函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且
通过自定义类加载器加入到 JVM 中。此时,如果没有双亲委派模型,那么 JVM 就可能误以为黑客自定义的
java.lang.String 类是系统的 String 类,导致“病毒代码”被执行。
		而有了双亲委派模型,黑客自定义的 java.lang.String 类永远都不会被加载进内存。因为首先是最顶端的类加
载器加载系统的 java.lang.String 类,最终自定义的类加载器无法加载 java.lang.String 类。
或许你会想,我在自定义的类加载器里面强制加载自定义的 java.lang.String 类,不去通过调用父加载器不就
好了吗?确实,这样是可行。但是,在 JVM 中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较
的类型的类加载器不同,那么会返回false。自定义的String无法通过校验。

如何实现双亲委派模型

  双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实 ClassLoader 类默认的 loadClass 方法已经帮我们写好了,我们无需去写。

loadClass 默认实现如下:

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

再看看 loadClass(String name, boolean resolve) 函数:

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
      long t0 = System.nanoTime();
      try {
        if (parent != null) {
          c = parent.loadClass(name, false);
       } else {
          c = findBootstrapClassOrNull(name);
       }
     } catch (ClassNotFoundException e) {
        // ClassNotFoundException thrown if class not found
        // from the non-null parent class loader
     }
      if (c == null) {
        // If still not found, then invoke findClass in order
        // to find the class.
        long t1 = System.nanoTime();
        c = findClass(name);
        // this is the defining class loader; record the stats
        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
        sun.misc.PerfCounter.getFindClasses().increment();
     }
   }
    if (resolve) {
      resolveClass(c);
   }
    return c;
 }
}

从上面代码可以明显看出, loadClass(String, boolean) 函数即实现了双亲委派模型!整个大致过程如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用 parent.loadClass(name, false); ).或者是调用 bootstrap 类加载器来加载。
  3. 如果父加载器及 bootstrap 类加载器都没有找到指定的类,那么调用当前类加载器的 findClass 方
    法来完成类加载。

如果自定义类加载器,就必须重写 findClass 方法!
findClass 的默认实现如下:

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

  可以看出,抽象类 ClassLoader 的 findClass 函数默认是抛出异常的。而前面我们知道, loadClass 在父加载
器无法加载类的时候,就会调用我们自定义的类加载器中的 findeClass 函数,因此我们必须要在 loadClass 这个函数里面实现将一个指定类名称转换为 Class 对象.
  如果是读取一个指定的名称的类为字节数组的话,这很好办。但是如何将字节数组转为 Class 对象呢?很简单,
Java 提供了 defineClass 方法,通过这个方法,就可以把一个字节数组转为Class对象

defineClass 默认实现如下:

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError {
    return defineClass(name, b, off, len, null);
}

defineClass 主要的功能是:将一个字节数组转为 Class 对象,这个字节数组是 class 文件读取后最终的字节数组。如,假设 class 文件是加密过的,则需要解密后作为形参传入 defineClass 函数。

四.自定义类加载器

为什么要自定义类加载器

  • 隔离加载类
    模块隔离,把类加载到不同的应用选中。比如tomcat这类web应用服务器,内部自定义了好几中类加载
    器,用于隔离web应用服务器上的不同应用程序。
  • 修改类加载方式
    除了Bootstrap加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
  • 扩展加载源
    比如还可以从数据库、网络、或其他终端上加载
  • 防止源码泄漏
    java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

自定义函数调用过程

在这里插入图片描述

自定义类加载器实现

实现方式:
所有用户自定义类加载器都应该继承ClassLoader类
在自定义ClassLoader的子类是,我们通常有两种做法:

  1. 重写loadClass方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制,不推荐)
  2. 重写findClass方法 (推荐)

样例

  1. 自定义类加载器,重写findClass方法
public class MyClassLoader extends ClassLoader {
    private String codePath;

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

    public MyClassLoader(String codePath) {
        this.codePath = codePath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;
        try {
            //1.字节码路径
            String fileName = codePath + name + ".class";
            //2.获取输入流
            bis = new BufferedInputStream(new FileInputStream(fileName));
            //3.获取输出流
            baos =new ByteArrayOutputStream();
            //4.io读写
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                baos.write(data, 0, len);
            }
            //5.获取内存中字节数组
            byte[] byteCode = baos.toByteArray();
            //6.调用defineClass 将字节数组转成Class对象
            Class<?> defineClass = defineClass(null, byteCode, 0, byteCode.length);
            return defineClass;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
  1. 定义一个待加载的普通Java类Test.java,生成class文件,放在某路径下。
  2. 主线程定义路径、文件名,使用自定义加载器进行加载
package com.lagou.unit2;
public class ClassLoaderTest {
  public static void main(String[] args) {
    MyClassLoader classLoader = new MyClassLoader("d:/");
    try {
      Class<?> clazz = classLoader.loadClass("TestMain");
      System.out.println("我是由"+clazz.getClassLoader().getClass().getName()+"类加载器加载的");
   } catch (ClassNotFoundException e) {
   	 e.printStackTrace();
   }
 }
}
// 执行结果:我是由com.loaderDemo.MyClassLoader类加载器加载的

五.ClassLoader源码剖析

类的关系图

在这里插入图片描述
在这里插入图片描述

Launcher核心类的源码剖析

在这里插入图片描述
先从启动类说起 ,有一个Launcher类 sun.misc.Launcher;做了四件事:

  1. 创建扩展类加载器
  2. 创建应用程序类加载器
  3. 设置ContextClassLoader
  4. 如果需要安装安全管理器 security manager

其中launcher是staitc的,所以初始化的时候就会创建对象,也就是触发了构造方法,所以初始化的时候就会执行上面四个步骤

public class Launcher {
  private static URLStreamHandlerFactory factory = new Launcher.Factory();
  //静态变量,初始化,会执行构造方法
  private static Launcher launcher = new Launcher();
  private static String bootClassPath = System.getProperty("sun.boot.class.path");
  private ClassLoader loader;
  private static URLStreamHandler fileHandler;
  public static Launcher getLauncher() {
    return launcher;
 }
//构造方法执行
  public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
      //1、初始化扩展类加载器
      var1 = Launcher.ExtClassLoader.getExtClassLoader();
   } catch (IOException var10) {
      throw new InternalError("Could not create extension class loader", var10);
   }
    try {
      //2、初始化应用类加载器
      this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
   } catch (IOException var9) {
      throw new InternalError("Could not create application class loader", var9);
   }
	//3、设置ContextClassLoader ,设置为扩展类加载器
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
      SecurityManager var3 = null;
      if (!"".equals(var2) && !"default".equals(var2)) {
        try {
          var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
       } catch (IllegalAccessException var5) {
       } catch (InstantiationException var6) {
       } catch (ClassNotFoundException var7) {
       } catch (ClassCastException var8) {
       }
     } else {
     //4、如果需要安装安全管理器 security manager
        var3 = new SecurityManager();
     }
      if (var3 == null) {
        throw new InternalError("Could not create SecurityManager: " + var2);
     }
      System.setSecurityManager(var3);
   }
 }

ExtClassLoader

static class ExtClassLoader extends URLClassLoader {
    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
      final File[] var0 = getExtDirs();
      try {
        return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
          public Launcher.ExtClassLoader run() throws IOException {
            int var1 = var0.length;
            for(int var2 = 0; var2 < var1; ++var2) {
              MetaIndex.registerDirectory(var0[var2]);
           }
            return new Launcher.ExtClassLoader(var0);
         }
       });
     } catch (PrivilegedActionException var2) {
        throw (IOException)var2.getException();
     }
   }
    void addExtURL(URL var1) {
      super.addURL(var1);
   }
    public ExtClassLoader(File[] var1) throws IOException {
      super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
      SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
   }
    private static File[] getExtDirs() {
      String var0 = System.getProperty("java.ext.dirs");
      File[] var1;
      if (var0 != null) {
        StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
        int var3 = var2.countTokens();
        var1 = new File[var3];
        for(int var4 = 0; var4 < var3; ++var4) {
          var1[var4] = new File(var2.nextToken());
       }
     } else {
        var1 = new File[0];
     }
      return var1;
   }
}

AppClassLoader

/**
* var1 类全名
* var2 是否连接该类
*/
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
      int var3 = var1.lastIndexOf(46);
      if(var3 != -1) {
        SecurityManager var4 = System.getSecurityManager();
        if(var4 != null) {
          var4.checkPackageAccess(var1.substring(0, var3));
       }
     }
      if(this.ucp.knownToNotExist(var1)) {//一般都是false,想要返回TRUE可能需要设置启动参数lookupCacheEnabled 为true。为true时,具体的逻辑也是C++写的,所以做了什么就不大清楚了。
        Class var5 = this.findLoadedClass(var1); //如果这个类已经被这个类加载器加载,则返回这个类,否则返回Null
        if(var5 != null) {
          if(var2) {
            this.resolveClass(var5); //如果该类没有被link(连接),则连接,否则什么都不做
         }
          return var5;
       } else {
          throw new ClassNotFoundException(var1);
       }
     } else {
        return super.loadClass(var1, var2);
     }

ClassLoader 源码剖析

ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),这里主要介绍ClassLoader中四个比较重要的方法:loadClass(String),findClass(String),defineClass(byte[] b, int off, int len),resolveClass(Class<?>c)

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

使用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类: 调用findLoadedClass(String)方法检查这
个类是否被加载过 使用父加载器调用loadClass(String)方法,如果父加载器为Null,类加载器装载虚拟机内置的加载器调
用findClass(String)方法装载类, 如果,按照以上的步骤成功的找到对应的类,并且该方法接收的resolve参数的值为
true,那么就调用resolveClass(Class)方法来处理类。 ClassLoader的子类最好覆盖findClass(String)而不是这个
方法。 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)

  • 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对象
    (defineClass中已实现该方法逻辑),通过这个方法不仅能够通过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对象创建完成也同时被解析。前面我们说链接阶段主要是对字节码进行验证,
    为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值