从零造就JVM大牛(一)
引言
从事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 = new NetworkClassLoader(host, port);
* Object main = loader.loadClass("Main", true).newInstance();
* . . .
* </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
* . . .
* }
* }
* </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™ 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™ 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-面试轮次 , 腾讯-乘车码-面试轮次 , 腾讯-微信支付-面试轮次 , 腾讯-零售新业务-面试轮次 , 腾讯-直播平台-面试轮次, 快手-广告业务部-面试轮次 , 贝壳找房-商品组-面试轮次 , 百度-信息流-面试轮次 , 京东-零售-面试轮次 , 京东-物流-面试轮次 , 京东-电商-面试轮次 , 滴滴-小桔车服-面试轮次 , 滴滴-金融-面试轮次 , 阿里-高德-面试轮次 , 阿里-大文娱-面试轮次 , 阿里-健康-面试轮次 , 阿里-蚂蚁金服-面试轮次 , 美团-外卖-面试轮次 , 美团-风控-面试轮次
从零造就JVM大牛(一)的更多相关文章
- JVM(零):走入JVM
JVM(零):走入JVM 本系列主要讲述JVM相关知识,作为本系列的第一篇文章,本文从Java为什么是一个跨平台的语音开始介绍,逐步引入Java虚拟机的概念,并给出一个JVM相关知识图谱,可以让读者从 ...
- 从零到Django大牛的的进阶之路02
Cookie/Session Cookie Cookie以键值对的格式进行信息的存储. Cookie基于域名安全,不同域名的Cookie是不能互相访问的,如访问itcast.cn时向浏览器中写了Coo ...
- 从零到Django大牛的的进阶之路01
搭建 创建虚拟环境 mkvirtualenv django_py3_1.11 -p python3 安装Django pip install django==1.11.11 创建工程 django-a ...
- JVM点滴
JVM java拥有GC,为什么还会内存泄漏? 理解什么是内存泄漏: Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露. Java为了简化编程工作,对于不再 ...
- 大牛整理最全Python零基础入门学习资料
大牛整理最全Python零基础入门学习资料 发布时间:『 2017-11-12 11:56 』 帖子类别:『人工智能』 阅读次数:3504 (本文『大牛整理最全Python零基础入门学习资料 ...
- [零] Java 语言运行原理 JVM原理浅析 入门了解简介 Java语言组成部分 javap命令使用
Java Virtual Machine 官方介绍 Java虚拟机规范官方文档 https://docs.oracle.com/javase/specs/index.html 其中以java8的为 ...
- 阿里P8Java大牛仅用46张图让你弄懂JVM的体系结构与GC调优。
本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.图文并茂不生枯燥. 此PPT长达46页,全部展示篇幅过长,本文优先分享前十六页 ...
- 从rocketmq入手,解析各种零拷贝的jvm层原理
在上一篇文章中,主要介绍了rocketmq消息的存储流程.其主要使用了mmap的零拷贝技术实现了硬盘和内存的映射,从而提高了读写性能.在流程中有一个非常有意思的预热方法并没有详细分析,因为其中涉及到了 ...
- 基础知识《零》---一张图读懂JDK,JRE,JVM的区别与联系
随机推荐
- Java基础一篇过(八)常见异常速查
一.引言 开发过程中可能会遇到各种各样的异常,这里还是汇总一些比较典型的异常,有些比较直观的异常如空指针这种就不写了,此文可作为异常速查用. 二.异常大军正在来袭~ IllegalArgumentEx ...
- Flutter音频播放--chewie_player的基本使用
发现网络似乎没有关于简单音频播放的插件介绍,这几天找了一下,结果也都不尽人意,最后也是debug一下chewie_player插件的官方demo 先上官方demo图 官方git地址:https://g ...
- Linux系统编程 —读写锁rwlock
读写锁是另一种实现线程间同步的方式.与互斥量类似,但读写锁将操作分为读.写两种方式,可以多个线程同时占用读模式的读写锁,这样使得读写锁具有更高的并行性. 读写锁的特性为:写独占,读共享:写锁优先级高. ...
- Java学习day03
day03 课堂笔记 1.数据类型 2.总结第二章到目前为止所学内容: * 标识符 * 关键字 * 字面值 * 变量 成员变量如果没有赋值,系统会自动赋值,而局部变量不手动赋值,则会编译不通过. * ...
- Java知识日常收集整理001Java获取变量的数据类型的实现方法
一.具体情况区分 对于简单类型变量,是无法直接获得变量类型的:要想获取,必须自定义函数进行返回. 对于包装类型变量,是可以直接获得的,变量名称.getClass().getName(); 二.代码实现 ...
- [C#.NET 拾遗补漏]09:数据标注与数据校验
数据标注(Data Annotation)是类或类成员添加上下文信息的一种方式,在 C# 通常用特性(Attribute)类来描述.它的用途主要可以分为下面这三类: 验证 Validation:向数据 ...
- Multipath QUIC (MPQUIC): Design and Evaluation
"Multipath QUIC: Design and Evaluation" https://multipath-quic.org/conext17-deconinck.pdf ...
- intelliJ 软件项目打开运行
1.导入项目 2.首先更改数据库,找到application-dev.yml文件,更改数据源 3.配置tomcat端口 找到application.yml 文件 然后打开pom.xml 更改版本号 ...
- 用Docker容器安装Jenkins
先安装Docker 可以参考我的上一篇文章 链接 拉取Jenkins最新镜像,可跟版本号 不跟默认拉取最新镜像 docker pull jenkins/jenkins 创建JenKins的工作目录 m ...
- vs code C语言环境搭建
最近重温C语言,因为很多练习只是小程序,并不需要Clion和Codeblocks这样工程导向的编译软件,所以下载了vs code,并试图搜索相应的环境,在此过程中发现,网上许多vs code 的c/c ...