引言

从事java的小伙伴大家好,如果你是一名从事java行业的程序员,无论你是小白还是工作多年的老司机,我相信这篇文章一定会给你带来

不同程度的收货不敢说你看完我的文章从此精通jvm打遍天下无对手,但我能保证的是看完我的文章并且实践操作加以理解,至少在jvm

的这个领域碾压百分之80以上的程序员。废话不多说我们进入正题。(此文连载,请持续关注!!!)

一:类加载

我相信很多从事java的小伙伴儿在网上或者视频上也看了很多关于jvm的文章和讲解,但总觉得缺少点儿什么,那么今天我来告诉你为什么

会有这种感觉,因为很多人或者说很多文章讲的jvm都没有从最底层,没有从人的习惯性思维上去剖析。从今天开始,我就要把jvm那点儿

事儿给小伙伴儿们说清楚。保证你会见到jvm领域的另一番美妙的天地。

废话不多说直接进入正题。讲类加载器之前,咱们先从类加载开始说起,那么java底层是如何加载一个类的呢,他的顺序是怎么样的呢?下面

我会细致的手把手带你分析。

在java代码中,类型的加载,连接,初始化过程都是在程序运行期间完成的.

类型加载:这里的类型指的的是什么呢?

答:类型就是指的我们Java源代码通过编译后的class文件

类型的来源有哪些?类型的来源我总结了以下7点内容

答:

1:本地磁盘

2:网络下载.class文件

3:war,jar下加载.class文件

4:从专门的数据库中读取.class文件(少见)

5:将java源文件动态编译成class文件

6:典型的就是动态代理,通过运行期生成class文件

7:我们的jsp会被转换成servlet,而我们的serlvet是一个java文件,会被编译成class文件

那么我们的是通过什么进行加载的呢?是如何被加载到jvm中的呢?

答:通过我们的类加载器(classLoader)进行加载

首先我们来了解以下类加载器的种类?系统级别的类加载器如下

1:启动类加载器 (Bootstrap Classloader)

2:扩展类加载器(Extention ClassLoader)

3:应用类加载器

非系统级别类加载器如下:

1:自定义类加载器

接下来我们一个一个类加载剖析,好好的讲讲他们的前世今生,以及作用,让你彻底的了解类加载器到底是什么。

一:启动类加载器(Bootstrap Classloader)重点剖析

加载的职责:负责加载JAVA_HOME/jre/lib/rt.jar。该加载器是有C++实现,不是ClassLoader类的子类。

接下来我们会以代码穿插的方式来讲清楚类加载器到底是什么。很多人只是用嘴说,这样当时听懂了,然而并不能

真正的体会到类加载器以及类加载的精髓。只有用示例去验证你的说法,这才是正确的学习姿势。废话不多说开搞。

首先创建一个java工程 jvm-classloader jdk版本采用1.8.0_144 包名为src/com.test 创建类 MainClass01,结构如下图:

首先我们通过代码的形式来看看,我们的启动类加载器到底加载了哪些包

package com.test;

import java.util.Arrays;
import java.util.List; /**
* jvm 类加载器 第一章
* @author 奇客时间-时光
* 打印启动类加载器加载的路径
*/
public class MainClass01 { public static void main(String[] args) { String bootStrapLoadingPath = System.getProperty("sun.boot.class.path");
List<String> bootLoadingPathList = Arrays.asList(bootStrapLoadingPath.split(";"));
for(String bootPath:bootLoadingPathList) {
System.out.println("启动类加载器加载的目录:{}"+bootPath);
}
}
} "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60329:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\sunrsasign.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar
启动类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\classes Process finished with exit code 0

通过以上运行代码可知,启动类加载器加载如下的这些jar包。包含了大家常说的rt.jar。那么现在有个问题,从运行结果上我们看启动类加载加载了C:\Program Files\Java\jdk1.8.0_144\jre\classes这个目录下的类。那么我们自己写一个类,编译成Class文件以后丢到这个目录下它会被加载嘛?我们不妨来试一下。我们在com.test包下面创建一个Cat类。然后找到编译好的Cat.class文件,扔到C:\Program Files\Java\jdk1.8.0_144\jre\classes目录下。

如图可知,我们jre目录并没有classes目录,所以我们手动创建一个,并且把Cat.class文件扔进去。然后在测试类中获取一下Cat.classLoader是什么,由此就可以知道Cat是不是被启动类加载器加载了。

由上面的运行结果可知,我们的Cat类确实被启动类加载器给加载了。为什么是null?当一个类被启动类加载器加载以后,那么他的getClassLoader返回的结果就是null,这也是启动类加载器和其他类加载器不一样的地方。

补充说明:Bootstrap Classloader加载器是由C++去加载的,然后Bootstrap Classloader加载rt等jar包,Bootstrap Classloader加载器也加载了系统类加载器和扩展类类加载器他们都位于sun.boot.class.path这个路径。

二:扩展类加载器(Extention ClassLoader)重点剖析

加载的职责:加载java平台扩展的jar包,\lib\ext,可以通过-Djava.ext.dirs指定加载的路径

该加载器是有java代码编写的,并且是ClassLoader的子类,位于sun.misc.Launcher$ExtClassLoader 是我们launch类的一个内部类

同样的我们在MainClass01中测试一下

package com.test;

import java.util.Arrays;
import java.util.List; /**
* jvm 类加载器 第一章
* @author 奇客时间-时光
* 打印启动类加载器加载的目录
* 打印扩展类加载器加载的目录
*/
public class MainClass01 { public static void main(String[] args) { String extLoadingPath = System.getProperty("java.ext.dirs");
List<String> list = Arrays.asList(extLoadingPath.split(";"));
for(String extpath:list) {
System.out.println("扩展类加载器加载的目录:{}"+extpath);
}
}
} "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=59761:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01
扩展类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext
扩展类加载器加载的目录:{}C:\WINDOWS\Sun\Java\lib\ext Process finished with exit code 0

三:应用类加载器(APP ClassLoader)重点剖析

加载的职责:负责加载我们工程目录下classpath下的文件下.class以及jar包

该加载器也是由Java代码编写的也是我们ClassLoader的一个子类,sun.misc.Launcher$AppClassLoader 是我们的launcher的内部类

String appLoadingPath = System.getProperty("java.class.path");

同样的,我们在MainClass01中运行如下代码:

package com.test;

import java.util.Arrays;
import java.util.List; /**
* jvm 类加载器 第一章
* @author 奇客时间-时光
* 打印启动类加载器加载的目录
* 打印扩展类加载器加载的目录
* 打印应用类加载器加载的目录
*/
public class MainClass01 { public static void main(String[] args) { String appLoadingPath = System.getProperty("java.class.path");
List<String> appLoadingPathList = Arrays.asList(appLoadingPath.split(";"));
for(String appPath:appLoadingPathList) {
System.out.println("应用类加载器加载的目录:{}"+appPath);
} }
} "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=52479:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass01
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar
应用类加载器加载的目录:{}C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar
应用类加载器加载的目录:{}I:\jvm\out\production\jvm-classloader
应用类加载器加载的目录:{}C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar Process finished with exit code 0

四:我们自定义的类加载器

由此可知,三种类加载所加载对应的目录已经介绍完毕,后面还会对这三种类加载器进行深入的解析。下面我们再看看自定义类加载器。为什么需要自定义类加载器呢?因为有些场景下jdk给我们提供的三种类加载器没有办法实现我们的个性化需求。关于为什么,这个我后面会详细阐述。我们首先看看自定义类加载器以及源码。

如何写自定义类加载器呢?首先我们看看jdk 提供的java原版 doc文档是如何描述的,这里要说一下,无论各位小伙伴以后学什么技术,第一手资料很重要。因为是最权威的,也没有经过任何加工的,也是最准确的。所以我们直接看源码中的doc文档即可。

那么我们要写自己的类加载器,对于小白来说肯定是不清楚如何下手的。这个时候,我们就要想,自定义类加载肯定是和类加载器有关系,不放我们看看java有没有提供关于类加载器相关的类。于是我们可以通过IDEA的编辑器进行搜索一下,类加载器顾名思义 ClassLoader先搜索一下。结果还真有。说明这是一个跟类加载器有关的类,我们直接阅读他的java doc。

我把之前编辑好的中文注释贴在下面。大家可以阅读,不过这里还是建议学习jdk源码的小伙伴还是自己阅读比较好,这样记得也比较深刻。

4.1:ClassLoader抽象类 文档解读

/**
* A class loader is an object that is responsible for loading classes(类加载器是一个对象,作用就是用来加载器类的). The
* class ClassLoader is an abstract class(本类是一个抽象的类). Given the binary name of a class
(给定一个类的二进制名字,比如java.lang.String就是String类的二进制名字)
, a class loader should attempt to
* locate or generate data that constitutes a definition for the class(那么类加载器尝试去定位(已经存在我们的磁盘文件中)
或者生成(为啥有生成,存在动态代理)构成class的定义数据).
A typical strategy is to transform the name into a file name and then read a
* "class file" of that name from a file system.(通常的策略就是把我们的类的二进制名称转换为我们的文件名称
比如java.lang.String 转换为java/lang/String)
那么就会根据这个文件名称去文件系统找到该class文件。
*
* <p> Every {@link Class <tt>Class</tt>} object contains a {@link
* Class#getClassLoader() reference} to the <tt>ClassLoader</tt> that defined
* it. (每一个通过类加载器加载的class文件后会返回一个Class对象,该Class对象会包含一个加载他的ClassLoader的对象引用)
*
* <p> <tt>Class</tt> objects for array classes are not created by class
* loaders, but are created automatically as required by the Java runtime.
我们的数组对象的Class对象不是由我们的类加载器创建的,而是由我们的jvm根据需要在运行时间创建出来的.
* The class loader for an array class, as returned by {@link
* Class#getClassLoader()} is the same as the class loader for its element
* type;(我们获取到了数组类型的CLass对象,通过该Class对象调用getClassLoader()返回的是跟我们数组元素中的类加载器是一样的)
if the element type is a primitive type, then the array class has no
* class loader.(如果我们原生的数组 那么该数组的Class没有类加载器)
* <p> Applications implement subclasses of <tt>ClassLoader</tt> in order to
* extend the manner in which the Java virtual machine dynamically loads
* classes.(我们通过实现ClassLoader,可以动态来扩展我们类加载的方式)
* <p> Class loaders may typically be used by security managers to indicate
* security domains.
* <p> The <tt>ClassLoader</tt> class uses a delegation model to search for
* classes and resources.(ClassLoader使用双亲委派模型来寻找类和资源) Each instance of <tt>ClassLoader</tt> has an
* associated parent class loader(classloader的每一个实例都会有一个parent的classLoader). When requested to find a class or
* resource, a <tt>ClassLoader</tt> instance will delegate the search for the
* class or resource to its parent class loader before attempting to find the
* class or resource itself(当我们发起类加载的请求,那么类加载器自己去寻找资源之前委托给父类). The virtual machine's built-in class loader,
* called the "bootstrap class loader", (虚拟机内嵌的classLoader是叫做启动类加载器)does not itself have a parent but may
* serve as the parent of a <tt>ClassLoader</tt> instance.(启动类加载器是没有双亲的,但是他可以作为其他类加载器的双亲)
* <p> Class loaders that support concurrent loading of classes are known as
* <em>parallel capable</em> class loaders(若一个类加载器支持并行加载的话,那么这个类加载器就叫做并行类加载器) and are required to register
* themselves at their class initialization time by invoking the
* {@link
* #registerAsParallelCapable <tt>ClassLoader.registerAsParallelCapable</tt>}
* method.(若一个类加载器想要成为一个并行类加载器的话,那么在该类加载器初始化的时候要求去调用ClassLoader.registerAsParallelCapable方法)
Note that the <tt>ClassLoader</tt> class is registered as parallel
* capable by default(默认情况下当前的这个类ClassLoader这个抽象类模式是并行加载器器). However, its subclasses still need to register themselves
* if they are parallel capable.(然而我们的子加载器需要成为并行的内加载器,需要注册自己为并行的类加载器) <br>
* In environments in which the delegation model is not strictly
* hierarchical(若我们的类加载器不是属于双亲委派的模型情况下), class loaders need to be parallel capable(类加载器需要注册为并行的加载器), otherwise class
* loading can lead to deadlocks because the loader lock is held for the
* duration of the class loading process (see {@link #loadClass
* <tt>loadClass</tt>} methods).(不然在内加载的阶段会导致死锁)
* <p> Normally, the Java virtual machine loads classes from the local file
* system in a platform-dependent manner(通常情况下 jvm从本地磁盘下去加载类). For example, on UNIX systems, the
* virtual machine loads classes from the directory defined by the
* <tt>CLASSPATH</tt> environment variable.(在unix系统中,虚拟机从classpath下加载类)
* <p> However, some classes may not originate from a file(然而,有些class文件不是存在我们的磁盘文件中); they may originate
* from other sources, such as the network(比如从网络上), or they could be constructed by an
* application(动态代理生产的). The method {@link #defineClass(String, byte[], int, int)
* <tt>defineClass</tt>} converts an array of bytes into an instance of class
* <tt>Class</tt>. (我们的defineClass方法会把字节数组转为我们一个class的实例)Instances of this newly defined class can be created using
* {@link Class#newInstance <tt>Class.newInstance</tt>}.这个实例可以通过我们的newInstance来调用
全盘委托模型:由我们classloader加载出来的class那么该类中的方法或构造函数可能引用其他类,jvm会调用同一个类加载器去加载被应用的类
* <p> The methods and constructors of objects created by a class loader may
* reference other classes(). To determine the class(es) referred to, the Java
* virtual machine invokes the {@link #loadClass <tt>loadClass</tt>} method of
* the class loader that originally created the class.
* <p> For example, an application could create a network class loader to
* download class files from a server. Sample code might look like:
* <blockquote><pre>
* ClassLoader loader&nbsp;= new NetworkClassLoader(host,&nbsp;port);
* Object main&nbsp;= loader.loadClass("Main", true).newInstance();
* &nbsp;.&nbsp;.&nbsp;.
* </pre></blockquote>
我们自定义的class laoder 需要重写我们的ClassLoader类的findClass 和我们的loadClassData方法
* <p> The network class loader subclass must define the methods {@link
* #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> to load a class
* from the network. Once it has downloaded the bytes that make up the class,
* it should use the method {@link #defineClass <tt>defineClass</tt>} to
* create a class instance. A sample implementation is:
* <blockquote><pre>
* class NetworkClassLoader extends ClassLoader {
* String host;
* int port;
* public Class findClass(String name) {
* byte[] b = loadClassData(name);
* return defineClass(name, b, 0, b.length);
* }
* private byte[] loadClassData(String name) {
* // load the class data from the connection
* &nbsp;.&nbsp;.&nbsp;.
* }
* }
* </pre></blockquote>
* <h3> <a name="name">Binary names</a> </h3>
* <p> Any class name provided as a {@link String} parameter to methods in
* <tt>ClassLoader</tt> must be a binary name as defined by
* <cite>The Java&trade; Language Specification</cite>.
* <p> Examples of valid class names include:
* <blockquote><pre>
* "java.lang.String" 表示我们的String类的二进制名称
* "javax.swing.JSpinner$DefaultEditor" 表示JSpinner的内部类DefaultEditor的二进制名称
* "java.security.KeyStore$Builder$FileBuilder$1" 表示java.security.KeyStore类的内部类Builder类的内部类的FileBuilder的第一个内部类
* "java.net.URLClassLoader$3$1" 表示java.net.URLClassLoader类中第三个内部类中的第一个内部类
* </pre></blockquote>
* @see #resolveClass(Class)
* @since 1.0
*/
public abstract class ClassLoader {}

通过上面文档的描述我们可以实现ClassLoader这个类就可以动态来扩展我们类加载的方式。从上面的文档中也可以看出,加载一个class文件需要用到loadClass方法,并且还要重写ClassLoader类的findClass 和我们的loadClassData方法

接下来对上述的几个重要的方法的java doc进行翻译

4.2:我们的ClassLoader类加载器重要方法详解

  • loadClass方法详解
/**
* Loads the class with the specified <a href="#name">binary name</a>. (根据二进制名称进行加载) The
* default implementation of this method searches for classes in the(下面是调用方法的顺序)
* following order:
* <ol>
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li> 调用findloaderClass检查类是否被加载过
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader(首先会调用父类的loaderClass来加载). If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used(若发现父类是null,那就说明已经到了顶层的启动类加载器),
instead. </p></li>
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li> 接下来就调用我们的findClass方法来查找class
* </ol>
* <p> If the class was found using the above steps(若通过上述步骤找到了class文件), and the
* <tt>resolve</tt> flag is true(那么就设置resolve为true), this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.(解析来那么就通过调用resolverClass
返回对象)
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
强烈的要求我们子类实现ClassLoader 那么我们必须要从写findClass方法
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
* @param name
* The <a href="#name">binary name</a> of the class
* @param resolve
* If <tt>true</tt> then resolve the class
* @return The resulting <tt>Class</tt> object
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//检查根据二进制名称是否被加载过
Class<?> c = findLoadedClass(name);
//没有加载过
if (c == null) {
long t0 = System.nanoTime();
try {
// 判断有没有父类,有父类 调用父类的loadClass
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();
//调用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) {
//解析我们的class进行连接 等操作
resolveClass(c);
}
return c;
}
}

上面这段java doc文档中有几个重要的点需要注意一下

1:根据二进制名称进行加载

2:调用findloaderClass检查类是否被加载过

3:首先会调用父类的loaderClass来加载

4:若发现父类是null,那就说明已经到了顶层的启动类加载器

5:接下来就调用我们的findClass方法来查找class

这几部是类加载器加载类的加载过程,比较重要的过程。希望各位小伙伴儿一定要结合源码牢记。

上述又提到了另一个方法

  • findClass()方法
/**
* Finds the class with the specified <a href="#name">binary name</a>.通过给定的二进制名称查找出class文件
* This method should be overridden by class loader implementations(这个方法应该被遵循双亲委托模型的子类重写) that
* follow the delegation model for loading classes(), and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class(这个方法会被loadClass方法调用,在父类加载器检查了是否加载过后). The default implementation
* throws a <tt>ClassNotFoundException</tt>.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
  • defineClass()方法
/**
这个方法的作用就是用来把字节数组转为一个Class对象,这个对象
* Converts an array of bytes into an instance of class <tt>Class</tt>.
* Before the <tt>Class</tt> can be used it must be resolved(这个Class对象能被使用必须是经过解析的因为在解析阶段会进行验证). This method
* is deprecated in favor of the version that takes a <a
* href="#name">binary name</a> as its first argument, and is more secure. 就是不推荐用这个方法,而是
推荐用它重写的方法,因为重写方法安全些(因为重写方法中一个参数是我们的是传入二进制模型,他是通过安全域进行保护的)
*
* @param b
* The bytes that make up the class data. The bytes in positions
* <tt>off</tt> through <tt>off+len-1</tt> should have the format
* of a valid class file as defined by
* <cite>The Java&trade; Virtual Machine Specification</cite>.
*
* @param off
* The start offset in <tt>b</tt> of the class data
*
* @param len
* The length of the class data
*
* @return The <tt>Class</tt> object that was created from the specified
* class data
*
* @throws ClassFormatError
* If the data did not contain a valid class
*
* @throws IndexOutOfBoundsException
* If either <tt>off</tt> or <tt>len</tt> is negative, or if
* <tt>off+len</tt> is greater than <tt>b.length</tt>.
*
* @throws SecurityException
* If an attempt is made to add this class to a package that
* contains classes that were signed by a different set of
* certificates than this class, or if an attempt is made
* to define a class in a package with a fully-qualified name
* that starts with "{@code java.}".
*
* @see #loadClass(String, boolean)
* @see #resolveClass(Class)
*
* @deprecated Replaced by {@link #defineClass(String, byte[], int, int)
* defineClass(String, byte[], int, int)}
*/
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}

以上两个方法在注释中均已经解释。

看完以上的java doc文档,那么我们就可以来写一个自定义的类加载器了。

创建自定义类加载的步骤如下:

1:创建一个类名为Test01ClassLoader,这个是我们自己的类加载器

2:继承ClassLoader类,并且重写findClass方法。

3:创建方法loadClassData构建一个byte[]数组。

4:在findClass中调用父类的defineClass方法,传入相关信息(参数name是名称,传入构建好的byte数组,起始下标从0开始,最后是数组长度)

5:构造函数不能少。如果带参数,意为你指定双亲加载器是谁(类加载器双亲委托模型后面文章中介绍,先记住自定义类加载器的写法和步骤即可)

6:最后就是在main函数中用我们自定义的类加载器去加载我们指定的类来测试。。。

package com.test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream; /**
* jvm 类加载器 第一章
* @author 奇客时间-时光
* 自定义类加载器
* 1,继承ClassLoader
* 2,通过构造器指定当前类加载器的父加载器
* 3,重写findClass方法
* 4,构造一个byte[]字数
* 5,调用this.defineClass方法创建classLoader的实例
*/
public class Test01ClassLoader extends ClassLoader { private String classLoaderName; //声明文件后缀
private final String x = ".class"; //声明文件路径
private String path; public void setPath(String path) {
this.path = path;
} /**
* 使用默认的类加载器作为当前类加载器的双亲{APPClassLoader作为双亲}
* @param classLoaderName
*/
public Test01ClassLoader(String classLoaderName){
super();//指定系统类加载器为我们的父加载器
this.classLoaderName = classLoaderName;
} /**
* 使用我们自己指定的类加载器作为当前类加载器的双亲
* @param parentClassLoaderNane
* @param classLoaderName
*/
public Test01ClassLoader(ClassLoader parentClassLoaderNane,String classLoaderName){
super(parentClassLoaderNane);
this.classLoaderName = classLoaderName;
} /**
* 重写findClass方法,这一步非常重要必不可少
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("自己的类加载器被加载了");
byte[] bytes = this.loadClassData(name);
return this.defineClass(name,bytes,0,bytes.length);
} /**
* 返回一个字节数字通过给定的类名
* @param name
* @return
*/
private byte[] loadClassData(String name ){ InputStream inputStream = null;
byte[] bytes = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
name = name.replace(".","\\");
inputStream= new FileInputStream(this.path+new File(name+this.x));
byteArrayOutputStream = new ByteArrayOutputStream(); int ch = 0;
while(-1 !=(ch= inputStream.read())){
byteArrayOutputStream.write(ch);
}
bytes=byteArrayOutputStream.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
inputStream.close();
byteArrayOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
return bytes;
} public static void main(String[] args) throws Exception {
//创建自定义类加载器的一个实例,并且通过构造器指定名称
Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1");
//设置加载路径
myClassLoader.setPath("I:\\jvm\\out\\production\\jvm-classloader\\");
//调用loadClass方法来加载我们的class文件
Class<?> classz = myClassLoader.loadClass("com.test.Dog");
//通过构造器创建实例
Object object = classz.getDeclaredConstructor().newInstance();
//查看我们创建的实例是由哪个类加载器加载的
System.out.println(object.getClass().getClassLoader()); /*myClassLoader.setPath("D:\\classes\\");
Class classz1 = myClassLoader.loadClass("com.jdyun.jvm05.Test");
Object object1 = classz1.getDeclaredConstructor().newInstance();
System.out.println(classz1.getClassLoader());
*/
/*System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
System.out.println(Test12.getSystemClassLoader());*/ /*MyTest02[] list = new MyTest02[1];
System.out.println(list.getClass().getClassLoader());*/ } }

上述代码运行结果显示还是通过AppClassLoader来加载的Dog类。那么这是为什么呢?因为我们在构造函数中指定的将系统类加载器也就是我们的AppClassLoader作为我们的双亲,那么根据双亲委托模型,父加载器能加载的类,就由父加载器来加载,所以我们自定义的类加载没有加载到Dog类。(双亲委托模型后面文章会详细介绍。先记住这个结论就好。)

接下来我们对代码进行一点儿改造,来测试我们自定义的类加载是否生效了。

我在G:\jdyun-jvm\out\production\jdyun-jvm\\com\\jdyun\\jvm05\\目录下创建一个class文件Test.class,运行结果如下图所示

我把加载的路径改变,这样我们我们的AppClassloader他只会加载classPath路径下的文件,而我们在外部指定的文件,就自然而然的被我们的自定义类加载器给加载了。

接下来我们对代码进行一下小小的改动,我们创建两个自定义类加载器的实例,然后看看加载出来的Class对象是不是一样的。

public static void main(String[] args) throws Exception {
//创建自定义类加载器的一个实例,并且通过构造器指定名称
Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1");
//设置加载路径
myClassLoader.setPath("G:\\jdyun-jvm\\out\\production\\jdyun-jvm\\");
//调用loadClass方法来加载我们的class文件
Class<?> classz = myClassLoader.loadClass("com.jdyun.jvm05.Test");
//通过构造器创建实例
Object object = classz.getDeclaredConstructor().newInstance();
//查看我们创建的实例是由哪个类加载器加载的
System.out.println(classz.getClassLoader());
System.out.println(classz.hashCode()); //创建自定义类加载器的一个实例,并且通过构造器指定名称
Test01ClassLoader myClassLoader2 = new Test01ClassLoader("loader1");
//设置加载路径
myClassLoader2.setPath("G:\\jdyun-jvm\\out\\production\\jdyun-jvm\\");
//调用loadClass方法来加载我们的class文件
Class<?> classz2 = myClassLoader2.loadClass("com.jdyun.jvm05.Test");
//通过构造器创建实例
Object object2 = classz2.getDeclaredConstructor().newInstance();
//查看我们创建的实例是由哪个类加载器加载的
System.out.println(classz2.getClassLoader());
System.out.println(classz2.hashCode()); } 打印结果:
自己的类加载器被加载了
com.test.Test01ClassLoader@1540e19d
21685669
自己的类加载器被加载了
com.test.Test01ClassLoader@7f31245a
325040804

通过上面代码可知,同一个类加载器加载的同一个Class文件加载出来的Class对象不是同一个。并且两个class对象之间也是不能相互转换的。

好了各位小伙伴儿,第一篇文章咱们就先介绍到这里,大家现在了解一下4种类加载器即可,此专题的文章后面还会陆续连载,期待各位小伙伴的阅读。

另外笔者在公众号:奇客时间,给大家收录了1000多道今年互联网公司的面试真题

面试真题-回复关键字形式:公司-部门-面试轮次,例如:阿里-蚂蚁金服-一面,自动回复面试真题;

当前已经收录如下:

字节跳动-抖音-面试轮次, 搜狐-搜索组-面试轮次, OPPO-商城-面试轮次, 58同城-基础架构部-面试轮次,湖南台-芒果TV-面试轮次 , 腾讯-乘车码-面试轮次 , 腾讯-微信支付-面试轮次 , 腾讯-零售新业务-面试轮次 , 腾讯-直播平台-面试轮次, 快手-广告业务部-面试轮次 , 贝壳找房-商品组-面试轮次 , 百度-信息流-面试轮次 , 京东-零售-面试轮次 , 京东-物流-面试轮次 , 京东-电商-面试轮次 , 滴滴-小桔车服-面试轮次 , 滴滴-金融-面试轮次 , 阿里-高德-面试轮次 , 阿里-大文娱-面试轮次 , 阿里-健康-面试轮次 , 阿里-蚂蚁金服-面试轮次 , 美团-外卖-面试轮次 , 美团-风控-面试轮次

最新文章

  1. Atitit webservice发现机制 WS-Discovery标准的规范attilax总结
  2. Core Animation - 核心动画
  3. tomcat7.0配置CORS(跨域资源共享)
  4. 剑指Offer 链表中倒数第k个结点
  5. 网页设计、java、Andorid资源清单整理
  6. Linux Shell查看磁盘分区,内存使用,CPU使用率
  7. 使用YUM管理软件包
  8. PHP的面向对象编程
  9. Razor视图引擎的基本概念与法语
  10. 自定义一个compass可编译的目录结构
  11. NChome导出补丁包需要注意的东西
  12. Cookie Version in J2EE
  13. 马航MH17事件将把普京逼入绝境?
  14. 数独GUI程序项目实现
  15. setup命令的安装
  16. Alpha集合
  17. Django REST framework+Vue 打造生鲜超市(二)
  18. Dynamics CRM2016 Web Api之查询查找字段的相关属性
  19. 菜鸟redis初学
  20. Jenkins安装及配置

热门文章

  1. chrome插件: yapi 接口TypeScript代码生成器
  2. Flutter学习三之搭建一个简单的项目框架
  3. luogu 3376 最小费用最大流 模板
  4. GOOGLE工作法(世界一速)|木深读书笔记
  5. 045 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 07 for循环应用及局部变量作用范围
  6. ThreeJS系列1_CinematicCameraJS插件详解
  7. Oracle 数据库下赋予用户的执行存储过程和创建表权限
  8. nginx的脚本引擎(一)
  9. linux内存优化之手工释放linux内存
  10. MarkDown语法记录,还在用word,txt编写项目文档吗?