【JVM】加载机制

2023-11-02

一.类装载子系统

介绍

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对象创建完成也同时被解析。前面我们说链接阶段主要是对字节码进行验证,
    为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【JVM】加载机制 的相关文章

  • PropertySources 中各种源的优先级

    Spring引入了新的注释 PropertySources对于所有标记为的类 Configuration since 4 0 需要不同的 PropertySource作为论证 PropertySources PropertySource c
  • 将儒略时间戳转换为 UNIX 中的常规时间

    我需要使用 Bash 将 UNIX 中的 Julian 时间戳转换为常规时间戳 在 Tandem OS 上 转换非常简单 例子 212186319010244541 OLSAPP SYSTST 1 gt interprettimestamp
  • spring - 强制 @Autowired 字段的 cglib 代理

    我有混合堆栈 EJB 和 Spring 为了将 Spring 自动装配到 EJB 我使用SpringBeanAutowiringInterceptor 不确定这是否会影响我遇到的问题 在尝试通过以下方式自动装配 bean 时 Scope p
  • 如何将 XMP XML 块序列化为现有的 JPEG 图像?

    我有许多 JPEG 图像 其中包含损坏的 XMP XML 块 我可以轻松修复这些块 但我不确定如何将 固定 数据写回图像文件 我目前正在使用 JAVA 但我愿意接受任何能让这项任务变得容易的事情 这是目标关于 XMP XML 的另一个问题
  • 尝试在没有 GatewayIntent 的情况下访问消息内容

    我希望每当我写一条打招呼的消息时 机器人都会在控制台中响应一条消息 但它只是给我一个错误 JDA MainWS ReadThread WARN JDA Attempting to access message content without
  • Shell 执行:时间与 /usr/bin/time

    当 bash zsh 执行以下操作时会发生什么 usr bin time l sleep 1 1 00 real 0 00 user 0 00 sys 516096 maximum resident set size 0 average s
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • tomcat 过滤所有 web 应用程序

    问题 我想对所有网络应用程序进行过滤 我创建了一个过滤器来监视对 apache tomcat 服务器的请求 举例来说 它称为 MyFilter 我在 netbeans 中创建了它 它创建了 2 个独立的目录 webpages contain
  • “./somescript.sh”和“. ./somescript.sh”有什么区别

    今天我按照一些说明在 Linux 中安装软件 有一个需要首先运行的脚本 它设置一些环境变量 指令告诉我执行 setup sh 但是我执行时犯了一个错误 setup sh 所以环境没有设置 最后我注意到了这一点并继续进行 我想知道这两种调用脚
  • tar.gz 和 tgz 是同一个东西吗? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我创建了 tgz 文件tar czvf filecommand then 我最终得到了一个 tgz 文件 我想知道它和tar gz 之间的
  • PHP 日志文件颜色

    我正在编写一个 PHP 日志文件类 但我想为写入文件的行添加颜色 我遇到的问题是颜色也会改变终端的颜色 我想要实现的是仅更改写入日志文件的行的颜色 class logClass extends Singleton private funct
  • ExceptionHandler 不适用于 Throwable

    我们的应用程序是基于 Spring MVC 的 REST 应用程序 我正在尝试使用 ExceptionHandler 注释来处理所有错误和异常 I have ExceptionHandler Throwable class public R
  • 使用 Java 从 S3 上的文件在 S3 上创建 zip 文件

    我在 S3 上有很多文件 需要对其进行压缩 然后通过 S3 提供压缩文件 目前 我将它们从流压缩到本地文件 然后再次上传该文件 这会占用大量磁盘空间 因为每个文件大约有 3 10MB 而且我必须压缩多达 100 000 个文件 所以一个 z
  • 阻止 OSX 变音符号为所有用户禁用 Java 中的 KeyBindings?

    注 我知道这个问题 https stackoverflow com questions 40335285 java keybinds stop working after holding down a key用户必须输入终端命令才能解决此问
  • struts 教程或示例

    我正在尝试在 Struts 中制作一个登录页面 这个想法是验证用户是否存在等 然后如果有错误 则返回到登录页面 错误显示为红色 典型的登录或任何表单页面验证 我想知道是否有人知道 Struts 中的错误管理教程 我正在专门寻找有关的教程 或
  • Spock模拟inputStream导致无限循环

    我有一个代码 gridFSFile inputStream bytes 当我尝试这样测试时 given def inputStream Mock InputStream def gridFSDBFile Mock GridFSDBFile
  • 从一个文本文件中获取数据并将其移动到新的文本文件

    我有一个文件 里面有数据 在我的主要方法中 我读入文件并关闭文件 我调用另一种方法 在原始文件的同一文件夹内创建一个新文件 所以现在我有两个文件 原始文件和通过我调用的方法生成的文件 我需要另一种方法 从原始文件中获取数据并将其写入创建的新
  • 如何将实例变量传递到 Quartz 作业中?

    我想知道如何在 Quartz 中外部传递实例变量 下面是我想写的伪代码 如何将 externalInstance 传递到此作业中 public class SimpleJob implements Job Override public v
  • Java 推断泛型类型

    我正在寻找类似的推断捕获泛型类型的概念 类似于以下方法片段 但不是捕获泛型类型的类 public
  • 尝试使用带有有效购买令牌的 Java Google Play Developer API v3 检索应用内购买信息时出现错误请求(无效值)

    当使用 Java Google Play Developer API 版本 3 并请求有效购买令牌的购买信息时 我收到以下异常 API 调用返回 400 Bad Request 响应以及以下消息 code 400 errors domain

随机推荐

  • Go语言面试题--基础语法(26)

    文章目录 1 下面这段代码能否正常结束 2 下面这段代码输出什么 为什么 3 下面代码是否能编译通过 如果通过 输出什么 1 下面这段代码能否正常结束 func main v int 1 2 3 for i range v v append
  • 安装nvm管理node版本详细步骤

    安装nvm管理node版本详细步骤 首先需要把已经安装的node进行卸载 注 卸载node之前 最好把node的版本记录一下 方便安装nvm之后可以下载你需要的node版本 具体卸载步骤如下 打开电脑的控制面板 gt 点击程序 gt 点击程
  • win7+nfs文件服务器,win7如何挂载nfs服务器

    在linux下面 除了samba 一种在局域网内的不同计算机之间提供文件及打印机等资源的共享服务 服务之外 我们还可以通过nfs服务共享文件 以达到跨本台访问的需求 下面是学习啦小编收集整理的win7如何挂载nfs服务器 希望对大家有帮助
  • 学 Rust 最好的图书之一,原版豆瓣 9.7分“封神之作”

    1 瓜王 争霸赛总冠军 开发者版图同步飞涨 Rust 大概是世界上 瓜 最多的编程语言了 最近的一个 瓜 来自微软 用 Rust 重写 Windows 内核 一边是不断有大厂使用 Rust 重构某个版块的老旧代码 一边是 Rust 内部传出
  • 企业场景篇

    企业场景篇 设计模式 简单工厂模式 工厂 factory 处理创建对象的细节 一旦有了SimpleCoffeeFactory CoffeeStore类中的orderCoffee 就变成此对象的客户 后期如果需要Coffee对象直接从工厂中获
  • 一句sql搞定Mysql删除数据后自增列从1开始

    在数据库应用 我们经常要用到唯一编号 以标识记录 在MySQL中可通过数据列的AUTO INCREMENT属性来自动生成 MySQL支持多种数据表 每种数据表的自增属性都有差异 这里将介绍各种数据表里的数据列自增属性 数据库中设置了自增列
  • RuntimeError: CUDA error: an illegal memory access was encountered 解决思路

    问题描述 在跑编译正常通过 CPU上也正常运行的某项目时 在运行到某个epoch时 程序突然出现以下错误 RuntimeError CUDA error an illegal memory access was encountered CU
  • 行业基础概念

    1 SoC称为系统级芯片 2 AHB Advanced High Performance Bus 译作高级高性能总线 3 DMA 全称Direct Memory Access 即直接存储器访问 DMA用来提供在外设和存储器之间或者存储器和存
  • SoloPi APP性能测试用

    这款名为 SoloPi 的小工具 作用在于监测安卓机的运行状态 包括 CPU 内存 乃至大家感受最直观的帧率等等 SoloPi 本身是非常良心的 首先它开源 保证了干净安全 其次它也没有广告和多余的后台进程 非常令人放心 SoloPi ht
  • 【MLOps】第 7 章 : 监控和反馈循环

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 加速中产 “返贫” 的4个迹象

    没有消息 就是好消息 这话放在现在的朋友圈子里 似乎很合适 最近接到两个朋友的电话 一个是朋友的诉苦电话 这位朋友曾是某大厂的高管 被裁后失业近1年 虽然当初赔了N 1 但架不住这位朋友 房贷近千万 配偶不工作 孩子送出国 即传说中的 中产
  • Win11系统默认用户名怎么进行修改教学

    Win11系统默认用户名怎么进行修改教学 安装了Win11系统之后 我们电脑的默认用户名是administrator 而有的用户想要将这个用户名进行个性化的修改 把它修改成为自己喜欢的名称 那么如何去修改默认用户名 接下来我们一起来看看具体
  • Python之“诗词大会”游戏

    需求分析 要先设计题库 然后在规定时间内循环随机出题 判断输入的答案是否正确 并统计答对问题的次数 如果答题时间到 则退出循环 结束答题 具体实现步骤 1 创建字典bank保存题库 问题为键 正确答案为值 问题使用元组保存 题干和选项为元组
  • 使用iostat分析IO性能

    对于I O bond类型的进程 我们经常用iostat工具查看进程IO请求下发的数量 系统处理IO请求的耗时 进而分析进程与操作系统的交互过程中IO方面是否存在瓶颈 下面通过iostat命令使用实例 说明使用iostat查看IO请求下发情况
  • 【神经网络】(9) 迁移学习(CNN-InceptionResNetV2),案例:交通标志4分类

    各位同学好 今天和大家分享一下Tensorflow2 0中使用迁移学习的方法 搭载InceptionResNetV2网络进行交通标志分类识别 1 网络简介 网络复现代码见下文 神经网络学习小记录35 Inception ResnetV2模型
  • Linux 中实用但很小众的 11 个炫酷终端命令

    今天给大家分享Linux总结出来的11个炫酷的Linux终端命令大全 通过今天这篇文章将向大家展示一系列的Linux命令 工具和技巧 我希望一开始就有人告诉我这些 而不是曾在我成长道路上绊住我 命令行日常系快捷键 如下的快捷方式非常有用 能
  • HTTP数据包头解析

    HTTP请求模型 一 连接至Web服务器 一个客户端应用 如Web浏览器 打开到Web服务器的HTTP端口的一个套接字 缺省为80 例如 http www myweb com 8080 index html 在Java中 这将等同于代码 S
  • 创建软连接和硬链接

    前言 硬链接的原理 使链接的两个文件共享同样的文件内容 也就是同样的 inode 硬链接有一个缺陷 只能创建指向文件的硬链接 不能创建指向目录的硬链接 但软链接可以指向文件或目录 软链接的原理 就跟我们在window ln 命令 创建链接
  • linux:linux高级命令(三)

    1 查找文件命令 1 1根据名称查找 find 在指定目录下查找文件 包括目录 在这里面递归找 选项 name 根据文件名 包括目录名 查找 eg find home beiyue desktop name 1 xt 不要在根目录下面查找因
  • 【JVM】加载机制

    JVM加载机制 一 类装载子系统 介绍 类加载器ClassLoader角色 类加载的执行过程 加载 链接 初始化 二 类加载器 介绍 分类 三 双亲委派模型 介绍 为什么需要双亲委派模型 如何实现双亲委派模型 四 自定义类加载器 为什么要自