三、一篇博文知悉类的加载过程(内附实例和图解!!!)

2023-11-23 18:59

本文主要是介绍三、一篇博文知悉类的加载过程(内附实例和图解!!!),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、为什么说Java语言是跨平台的?

Java语言之所以说它是跨平台的、可以在当前绝大部分的操作系统平台下运行,是因为Java语言的运行环境是在Java虚拟机中Java虚拟机消除了各个平台之间的差异,只要操作系统平台下安装了Java虚拟机,那么使用Java开发的东西都能在其上面运行。
如下图所示:在这里插入图片描述
Java虚拟机对各个平台而言,实质上是各个平台上的一个可执行程序。例如在windows平台下,java虚拟机对于windows而言,就是一个java.exe进程而已。

二、Java虚拟机启动、加载类过程分析

1.JVM启动的过程

下面我将定义一个非常简单的java程序并运行它,来逐步分析java虚拟机启动的过程。

package org.luanlouis.jvm.load;
import sun.security.pkcs11.P11Util;
/*** Created by louis on 2016/1/16.*/
public class Main{public static void main(String[] args) {System.out.println("Hello,World!");ClassLoader loader = P11Util.class.getClassLoader();System.out.println(loader);}
}

在windows命令行下输入: java org.luanlouis.jvm.load.Main 当输入上述的命令时: windows开始运行{JRE_HOME}/bin/java.exe程序,java.exe 程序将完成以下步骤:

  1. 根据JVM内存配置要求,为JVM申请特定大小的内存空间
  2. 创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;
  3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader
  4. 使用上述获取的ClassLoader实例加载我们定义的 org.luanlouis.jvm.load.Main类
  5. 加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;
  6. 结束,java程序运行结束,JVM销毁。

Step 1.根据JVM内存配置要求,为JVM申请特定大小的内存空间

为了不降低本文的理解难度,这里就不详细介绍JVM内存配置要求的话题,今概括地介绍一下内存的功能划分。 ​
JVM启动时,按功能划分,其内存应该由以下几部分组成: ​ 如上图所示,JVM内存按照功能上的划分,可以粗略地划分为 ​方法区(Method Area)和堆(Heap),而所有的类的定义信息都会被加载到方法区中。 ​
关于具体方法区里有什么内容,读者可以参考我的另一篇博文:Java虚拟机与跨平台原理讲解(硬怼面试官再也不怂!!!)

Step 2. 创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;

JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类。
java.lang.String, java.lang.Object等等。
引导类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包和配置,
然后将这些系统类加载到方法区内。

本例中,引导类加载器是用 {JRE_HOME}/lib加载类的,不过,你也可以使用参数 -Xbootclasspath 或
系统变量sun.boot.class.path来指定的目录来加载类。
一般而言,{JRE_HOME}/lib下存放着JVM正常工作所需要的系统类,如下表所示: | 文件名 | 描述 | rt.jar |
运行环境包,rt即runtime,J2SE 的类定义都在这个包内

| charsets.jar | 字符集支持包 | jce.jar | 是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现 | jsse.jar | 安全套接字拓展包Java™ Secure Socket Extension | classlist | 该文件内表示是引导类加载器应该加载的类的清单 | net.properties | JVM 网络配置信息 | 引导类加载器(Bootstrap ClassLoader)** 加载系统类后,JVM内存会呈现如下格局:

这里写图片描述
引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,在方法区中它应该有 运行时常量池类型信息 字段信息 方法信息 类加载器的引用 对应class实例的引用`等信息。

类加载器的引用

由于这些类是由引导类加载器(Bootstrap Classloader)进行加载的,而 引导类加载器是有C++语言实现的,所以是无法访问的,故而该引用为NULL 对应class实例的引用 类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

小测试:

当我们在代码中尝试获取系统类如java.lang.Object的类加载器时,你会始终得到NULL

System.out.println(String.class.getClassLoader());//null
System.out.println(Object.class.getClassLoader());//null
System.out.println(Math.class.getClassLoader());//null
System.out.println(System.class.getClassLoader());//null

Step 3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader

上述步骤完成,JVM基本运行环境就准备就绪了。接着,我们要让JVM工作起来了:
运行我们定义的程序 org.luanlouis,jvm.load.Main。
此时,JVM虚拟机调用已经加载在方法区的类sun.misc.Launcher 的静态方法getLauncher(), 获取sun.misc.Launcher 实例: sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher();
//获取Java启动器 ClassLoader classLoader = launcher.getClassLoader();
//获取类加载器ClassLoader用来加载class到内存来 sun.misc.Launcher 使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
在Launcher的内部,其定义了两个类加载器(ClassLoader),分别是
sun.misc.Launcher.ExtClassLoader
sun.misc.Launcher.AppClassLoader*,
这两个类加载器分别被称为拓展类加载器(Extension ClassLoader)*** 和 应用类加载器(Application ClassLoader).如下图所示:
在这里插入图片描述

​ 除了引导类加载器(Bootstrap Class Loader )的所有类加载器,都有一个能力,就是判断某一个类是否被引导类加载器加载过,如果加载过,可以直接返回对应的Class instance,如果没有,则返回null. 图上的指向引导类加载器的虚线表示类加载器的这个有限的访问 引导类加载器的功能。 ​

   此时的 launcher.getClassLoader() 方法将会返回 AppClassLoader 实例,AppClassLoader将ExtClassLoader作为自己的父加载器。 当AppClassLoader加载类时,会首先尝试让父加载器ExtClassLoader进行加载,如果父加载器ExtClassLoader加载成功,则AppClassLoader直接返回父加载器ExtClassLoader加载的结果;如果父加载器ExtClassLoader加载失败,AppClassLoader则会判断该类是否是引导的系统类(即是否是通过Bootstrap类加载器加载,这会调用Native方法进行查找);若要加载的类不是系统引导类,那么ClassLoader将会尝试自己加载,加载失败将会抛出“ClassNotFoundException”。 具体AppClassLoader的工作流程如下所示:
img

Step 4. 使用类加载器ClassLoader加载Main类

​        通过 launcher.getClassLoader()方法返回AppClassLoader实例,接着就是AppClassLoader加载 org.luanlouis.jvm.load.Main类的时候了。
         ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader类 classLoader.loadClass(“org.luanlouis.jvm.load.Main”);//加载自定义类 上述定义的org.luanlouis.jvm.load.Main类被编译成org.luanlouis.jvm.load.Main class二进制文件,这个class文件中有一个叫常量池(Constant Pool)的结构体来存储该class的常量信息。常量池中有CONSTANT_CLASS_INFO类型的常量,表示该class中声明了要用到那些类: ​ 当AppClassLoader要加载 org.luanlouis.jvm.load.Main类时,会去查看该类的定义,发现它内部声明使用了其它的类: sun.security.pkcs11.P11Util、java.lang.Object、java.lang.System、java.io.PrintStream、java.lang.Class;org.luanlouis.jvm.load.Main类要想正常工作,首先要能够保证这些其内部声明的类加载成功。所以AppClassLoader要先将这些类加载到内存中。

(注:为了理解方便,这里没有考虑懒加载的情况,事实上的JVM加载类过程比这复杂的多) 加载顺序:
1.  加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class*
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;
2. 而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class实例;
3. 加载sun.security.pkcs11.P11Util** 此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。
4. ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,加载成功后直接返回对应的Class<sun.security.pkcs11.P11Util>实例;
5. 加载org.luanlouis.jvm.load.Main** AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;
6. AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。而结果表明BootstrapClassLoader
没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,然后返回对应的Class<org.luanlouis.jvm.load.Main>实例引用;

**以上三步骤都成功,才表示classLoader.loadClass("org.luanlouis.jvm.load.Main")完成**,

上述操作完成后,JVM内存方法区的格局会如下所示: img
JVM方法区的类信息区是按照类加载器进行划分的,每个类加载器会维护自己加载类信息;
某个类加载器在加载相应的类时,会相应地在JVM内存堆(Heap)中创建一个对应的Class,用来表示访问该类信息的入口

Step 5. 使用Main类的main方法作为程序入口运行程序

Step 6. 方法执行完毕,JVM销毁,释放内存

三、类加载器有哪些?其组织结构是怎样的?

​ 类加载器(Class Loader):顾名思义,指的是可以加载类的工具。JVM自身定义了三个类加载器:引导类加载器(Bootstrap Class Loader)、拓展类加载器(Extension Class Loader )、应用加载器(Application Class Loader)。当然,我们有时候也会自己定义一些类加载器来满足自身的需要。

​ 引导类加载器(Bootstrap Class Loader): 该类加载器使JVM使用C/C底层代码实现的加载器,用以加载JVM运行时所需要的系统类,这些系统类在{JRE_HOME}/lib目录下。****由于类加载器是使用平台相关的底层C/C语言实现的, 所以该加载器不能被Java代码访问到。但是,我们可以查询某个类是否被引导类加载器加载过。***我们经常使用的系统类如:java.lang.String,java.lang.Object,java.lang… 这些都被放在 {JRE_HOME}/lib/rt.jar包内, 当JVM系统启动的时候,引导类加载器会将其加载到 JVM内存的方法区中。

​ 拓展类加载器(Extension Class Loader): 该加载器是用于加载 java 的拓展类 ,拓展类一般会放在 {JRE_HOME}/lib/ext/ 目录下,用来提供除了系统类之外的额外功能。拓展类加载器是是整个JVM加载器的Java代码可以访问到的类加载器的最顶端,即是超级父加载器,拓展类加载器是没有父类加载器的。

应用类加载器(Applocatoin Class Loader): 该类加载器是用于加载用户代码,是用户代码的入口。我经常执行指令 java xxx.x.xxx.x.x.XClass , 实际上,JVM就是使用的AppClassLoader加载 xxx.x.xxx.x.x.XClass 类的****。应用类加载器将拓展类加载器当成自己的父类加载器,当其尝试加载类的时候,首先尝试让其父加载器-拓展类加载器加载;如果拓展类加载器加载成功,则直接返回加载结果Class instance,加载失败,则会询问是否引导类加载器已经加载了该类;只有没有加载的时候,应用类加载器才会尝试自己加载。由于******xxx.x.xxx.x.x.XClass********是整个用户代码的入口,在Java虚拟机规范中,称其为 初始类(Initial Class). ** ​ *用户自定义类加载器(Customized Class Loader):*用户可以自己定义类加载器来加载类。所有的类加载器都要继承java.lang.ClassLoader类。

img

四、双亲加载模型的逻辑和底层代码实现是怎样的?

​ 上面已经不厌其烦地讲解什么是双亲加载模型,以及其机制是什么,这些东西都是可以通过底层代码查看到的。 ​ 我们也可以通过JDK源码看java.lang.ClassLoader的核心方法 loadClass()的实现:

//提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class<T> instance 实例
public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
} 
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class<T>实例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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//是否解析类  if (resolve) {resolveClass(c);}return c;}}

相对应地,我们可以整理出双亲模型的工作流程图:

img
相信读者看过这张图后会对双亲加载模型有了非常清晰的脉络。当然,这是JDK自身默认的加载类的行为,我们可以通过继承复写该方法,改变其行为。

五、类加载器与Class 实例的关系

img

六、线程上下文加载器

​ Java 任何一段代码的执行,都有对应的线程上下文。如果我们在代码中,想看当前是哪一个线程在执行当前代码,我们经常是使用如下方法:

Thread thread = Thread.currentThread();//返回对当当前运行线程的引用 相应地,我们可以为当前的线程指定类加载器。在上述的例子中, 当执行 java org.luanlouis.jvm.load.Main 的时候,JVM会创建一个Main线程,而创建应用类加载器AppClassLoader的时候,会将AppClassLoader 设置成Main线程的上下文类加载器:

 public Launcher() {Launcher.ExtClassLoader var1;try {var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10);}try {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}//将AppClassLoader设置成当前线程的上下文加载器Thread.currentThread().setContextClassLoader(this.loader);}

线程上下文类加载器是从线程的角度来看待类的加载,为每一个线程绑定一个类加载器,可以将类的加载从单纯的 双亲加载模型解放出来,进而实现特定的加载需求。

七.双亲委派机制

上面讨论的应用类加载器AppClassLoader的加载类的模式就是我们常说的双亲委派模型(parent-delegation model). 对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

  • \1. 委托父类加载器帮忙加载;
  • \2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
  • \3.如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
  • \4. 若加载成功,返回 对应的Class对象;若失败,抛出异常“ClassNotFoundException”。

请注意: 双亲委派模型中的"双亲"并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。上面的步骤中,有两个角色: \1.
父类加载器(parent classloader):它可以替子加载器尝试加载类 \2. 引导类加载器(bootstrap
classloader):
子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;换句话说,就是子类加载器不能接触到引导类加载器,引导类加载器对其他类加载器而言是透明的。

一般情况下,双亲加载模型如下所示:
img

八 、自定义类加载器


8.1 ClassLoader类加载器

主要的作用是将class文件加载到jvm虚拟机中。jvm启动的时候,并不是一次性加载所有的类,而是根据需要动态去加载类,主要分为隐式加载和显示加载。

隐式加载:程序代码中不通过调用ClassLoader来加载需要的类,而是通过JVM类自动加载需要的类到内存中。例如,当我们在类中继承或者引用某个类的时候,JVM在解析当前这个类的时,发现引用的类不在内存中,那么就会自动将这些类加载到内存中。

显示加载:代码中通过Class.forName(),this.getClass.getClassLoader.LoadClass(),自定义类加载器中的findClass()方法等。

8.2 jvm自带的加载器

(1)BootStrap ClassLoader:主要加载%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
可以通System.getProperty(“sun.boot.class.path”)

查看加载的路径,如下:😉

package test;public class TestGC {public static void main(String []args){System.out.println(System.getProperty("sun.boot.class.path"));}
}

显示结果如下:

D:\Program Files\Java\jdk1.7.0_45\jre\lib\resources.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\rt.jar;D:\Program 
Files\Java\jdk1.7.0_45\jre\lib\sunrsasign.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jsse.jar;D:\Program 
Files\Java\jdk1.7.0_45\jre\lib\jce.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\charsets.jar;D:\Program 
Files\Java\jdk1.7.0_45\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.7.0_45\jre\classes

(2)Extention ClassLoader:主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。也可以通过System.out.println(System.getProperty(“java.ext.dirs”))查看加载类文件的路径。

(3)AppClassLoader:主要加载当前应用下的classpath路径下的类。之前我们在环境变量中配置的classpath就是指定AppClassLoader的类加载路径。

8.3 类加载器的继承关系

先看一下这三个类加载器之间的继承关系,如下图:

img

ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader,BoopStrap ClassLoder不在上图中,因为它是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。*jvm加载的顺序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder,

*下面看一段源码:😉


```java
public class Launcher {private static Launcher launcher = new Launcher();private static String bootClassPath =System.getProperty("sun.boot.class.path");public static Launcher getLauncher() {return launcher;}private ClassLoader loader;public Launcher() {// Create the extension class loaderClassLoader extcl;try {extcl = ExtClassLoader.getExtClassLoader();} catch (IOException e) {throw new InternalError("Could not create extension class loader", e);}// Now create the class loader to use to launch the applicationtry {loader = AppClassLoader.getAppClassLoader(extcl);} catch (IOException e) {throw new InternalError("Could not create application class loader", e);}Thread.currentThread().setContextClassLoader(loader);}/** Returns the class loader used to launch the main application.*/public ClassLoader getClassLoader() {return loader;}/** The class loader used for loading installed extensions.*/static class ExtClassLoader extends URLClassLoader {}/*** The class loader used for loading from java.class.path.* runs in a restricted security context.*/static class AppClassLoader extends URLClassLoader {}

[复制代码](javascript:void(0)😉

从源码中我们看到:(1)Launcher初始化的时候创建了ExtClassLoader以及AppClassLoader,并将ExtClassLoader实例传入到AppClassLoader中。

(2)虽然上一段源码中没见到创建BoopStrap ClassLoader,但是程序一开始就执行了System.getProperty(“sun.boot.class.path”)。

8.4 类加载器之间的父子关系

AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为null,BoopStrap ClassLoader为顶级加载器。

下面一个小例子就可以证明,如下:新建一个Test类,可以通过getParent()方法获取上一层父机载器,执行如下代码:😉

package test;public class TestGC {public static void main(String []args){System.out.println(Test.class.getClassLoader().toString());System.out.println(Test.class.getClassLoader().getParent().toString());System.out.println(Test.class.getClassLoader().getParent().getParent().toString());}
}

输出结果如下:

img

8.5 类加载机制-双亲委托机制**

例如:当jvm要加载Test.class的时候,

(1)首先会到自定义加载器中查找,看是否已经加载过,如果已经加载过,则返回字节码。

(2)如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。

(3)如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。

(4)如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。

(5)如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下(“sun.boot.class.path**”)查看是否有Test.class字节码,有则返回,没有通

知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看。

(6)依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。如下图:

img

8.6 类加载过程的几个方法

(1)loadClass
(2)findLoadedClass
(3)findClass

😉

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) {//父加载器不为空,调用父加载器的loadClassc = parent.loadClass(name, false);} else {//父加载器为空则,调用Bootstrap Classloaderc = 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();//父加载器没有找到,则调用findclassc = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {//调用resolveClass()resolveClass(c);}return c;}}

8.7 自定义类加载器步骤

(1)继承ClassLoader
(2)重写findClass()方法
(3)调用defineClass()方法

下面写一个自定义类加载器:指定类加载路径在D盘下的lib文件夹下。

(1)新建一个Test.class类,代码如下:😉

package com.test;public class Test {public void say(){System.out.println("Hello MyClassLoader");}}

(2)cmd控制台执行javac Test.java,将生成的Test.class文件放到D盘lib文件夹->com文件夹->test文件夹下。

(3)自定义类加载器,代码如下:😉

package test;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;public class MyClassLoader extends ClassLoader{private String classpath;public MyClassLoader(String classpath) {this.classpath = classpath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte [] classDate=getDate(name);if(classDate==null){}else{//defineClass方法将字节码转化为类return defineClass(name,classDate,0,classDate.length);}} catch (IOException e) {e.printStackTrace();}return super.findClass(name);}//返回类的字节码private byte[] getDate(String className) throws IOException{InputStream in = null;ByteArrayOutputStream out = null;String path=classpath + File.separatorChar +className.replace('.',File.separatorChar)+".class";try {in=new FileInputStream(path);out=new ByteArrayOutputStream();byte[] buffer=new byte[2048];int len=0;while((len=in.read(buffer))!=-1){out.write(buffer,0,len);}return out.toByteArray();} catch (FileNotFoundException e) {e.printStackTrace();}finally{in.close();out.close();}return null;}
}

测试代码如下:😉

package test;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class TestMyClassLoader {public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{//自定义类加载器的加载路径MyClassLoader myClassLoader=new MyClassLoader("D:\\lib");//包名+类名Class c=myClassLoader.loadClass("com.test.Test");if(c!=null){Object obj=c.newInstance();Method method=c.getMethod("say", null);method.invoke(obj, null);System.out.println(c.getClassLoader().toString());}}
}

输出结果如下:

img

自定义类加载器的作用:
jvm自带的三个加载器只能加载指定路径下的类字节码。如果某个情况下,我们需要加载应用程序之外的类文件呢?比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了。



上一篇:深究JVM垃圾回收(保姆式讲解,内附大量图解!!!)

下一篇:

这篇关于三、一篇博文知悉类的加载过程(内附实例和图解!!!)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/420061

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

实例:如何统计当前主机的连接状态和连接数

统计当前主机的连接状态和连接数 在 Linux 中,可使用 ss 命令来查看主机的网络连接状态。以下是统计当前主机连接状态和连接主机数量的具体操作。 1. 统计当前主机的连接状态 使用 ss 命令结合 grep、cut、sort 和 uniq 命令来统计当前主机的 TCP 连接状态。 ss -nta | grep -v '^State' | cut -d " " -f 1 | sort |

Solr 使用Facet分组过程中与分词的矛盾解决办法

对于一般查询而言  ,  分词和存储都是必要的  .  比如  CPU  类型  ”Intel  酷睿  2  双核  P7570”,  拆分成  ”Intel”,”  酷睿  ”,”P7570”  这样一些关键字并分别索引  ,  可能提供更好的搜索体验  .  但是如果将  CPU  作为 Facet  字段  ,  最好不进行分词  .  这样就造成了矛盾  ,  解决方法

Java Websocket实例【服务端与客户端实现全双工通讯】

Java Websocket实例【服务端与客户端实现全双工通讯】 现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发 出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏 览器需要不断的向服务器发出请求,然而HTTP

Python:豆瓣电影商业数据分析-爬取全数据【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】

**爬取豆瓣电影信息,分析近年电影行业的发展情况** 本文是完整的数据分析展现,代码有完整版,包含豆瓣电影爬取的具体方式【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】   最近MBA在学习《商业数据分析》,大实训作业给了数据要进行数据分析,所以先拿豆瓣电影练练手,网络上爬取豆瓣电影TOP250较多,但对于豆瓣电影全数据的爬取教程很少,所以我自己做一版。 目

图解TCP三次握手|深度解析|为什么是三次

写在前面 这篇文章我们来讲解析 TCP三次握手。 TCP 报文段 传输控制块TCB:存储了每一个连接中的一些重要信息。比如TCP连接表,指向发送和接收缓冲的指针,指向重传队列的指针,当前的发送和接收序列等等。 我们再来看一下TCP报文段的组成结构 TCP 三次握手 过程 假设有一台客户端,B有一台服务器。最初两端的TCP进程都是处于CLOSED关闭状态,客户端A打开链接,服务器端