类的加载是指将类的.class文件中二进制数据读入到内存中,而后将其放在运行时数据区的方法区
内,而后在内存中建立爱你一个java.lang.Class
对象java
规范并无说明Class对象应该存放在哪,HotSpot虚拟机将其放在方法区中,用来封装类在方法区内的数据结构mysql
servlet技术sql
类加载器用来把类加载到Java虚拟机中,从JDK1.2版本开始,类的加载过程采用双亲委托机制
,这种机制能保证Java平台的安全性.数据库
从源码文档中翻译应该称为父类委托模式bootstrap
类加载器并不须要等到某个类被首次主动使用
时再加载它数组
程序首次主动
使用该类时才报告错误(LinkageError)根加载器没有父加载器
,主要负责虚拟机的核心类库,如java.lang.*
等,java.lang.Object
是由根类加载器加载的,根类加载器的实现依赖于底层操做系统
,属于虚拟机实现第一部分,它并无继承java.lang.ClassLoader类. 启动类加载器是特定于平台的机器指令,它负责开启整个加载过程 启动类加载器还会负责加载JRE正常运行所需的基本组件.其中包括java.util
,java.lang
包中的类安全
扩展类加载器的父加载器是根加载器
,从java.ext.dirs
系统属性指定的目录中加载类库,或者再jre\lib\ext
子目录下加载类库,若是把用户建立的JAR文件放在这个目录下,会自动由扩展类加载器
加载,扩展类加载器是纯Java类,是ClassLoader的子类bash
注意一点的是,拓展类加载器加载的是jar包内的class文件网络
系统类加载器
的父加载器为扩展类加载器
,从环境变量classpath或者系统属性java.class.path
所制定的目录加载类,它是用户自定义的类加载器的默认父加载器,系统类加载器是纯Java类,是ClassLoader的子类数据结构
除了虚拟机自带的加载器外,用户能够定制本身的类加载器.Java提供了抽象类ClassLoader.全部用户自定义的加载器都应该继承ClassLoader
AppClassLoader和ExtClassLoader都是Java类,因此须要类加载器进行加载,而这两个类的类加载器就是bootstrapClassLoader
能够经过修改 System.getProperty(java.system.class.loader)对默认的SystemClassLoader进行修改
在父亲委托机制中,各个加载器按照父子关系造成树形结构,除了根加载器以外,其他的类加载器有且只有一个父加载器.
简单描述,就是一个类加载器要加载一个类,并非由自身进行直接加载,而是经过向上寻找父加载器,直到没有父加载器的类加载器,而后再从上至下尝试加载,直至找到一个能够正确加载的类加载器,通常状况下,系统类加载器就能加载普通的类.
并非全部的类加载器都必须遵照双亲委托的机制,具体实现能够根据须要进行改造
public class Test08 {
public static void main(String[] args) {
try {
Class<?> clzz = Class.forName("java.lang.String");
//若是返回null,证实是由BootStrap加载器进行加载的
System.out.println(clzz.getClassLoader());
Class<?> customClass = Class.forName("com.r09er.jvm.classloader.Custom");
System.out.println(customClass.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Custom{
}
复制代码
输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
复制代码
String的类加载器为null,证实String是由Bootstrap类加载器加载,由于根加载器是由C++实现.因此会返回null
.
Custom的类加载器是Launcher$AppClassLoader,这个类是不开源的.可是是默认的系统(应用)类加载器
.
经过ClassLoader手动加载类,观察是否会触发类的初始化
public class Test12 {
public static void main(String[] args) throws Exception {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> aClass = loader.loadClass("com.r09er.jvm.classloader.TestClassLoader");
System.out.println(aClass);
System.out.println("-------");
aClass = Class.forName("com.r09er.jvm.classloader.TestClassLoader");
System.out.println(aClass);
}
}
class TestClassLoader{
static {
System.out.println("Test classloader");
}
}
复制代码
输出
class com.r09er.jvm.classloader.TestClassLoader
-------
Test classloader
class com.r09er.jvm.classloader.TestClassLoader
复制代码
结论
明显能够看出,classLoader.load方法加载类,类并不会初始化,说明不是对类的主动使用,调用了Class.ForName
才进行初始化
打印类加载器,因为根加载器由C++编写,因此就会返回null
public static void main(String[] args) {
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
//向上遍历父classLoader
while (null != loader) {
loader = loader.getParent();
System.out.println(loader);
}
}
复制代码
输出结果
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@610455d6
null
复制代码
clazz.getClassLoader()
Thread.currentThread().getContextLoader()
ClassLoader.getSystemClassLoader()
DriverManager.getClassLoader()
类加载器是负责加载类
的对象,classLoader是抽象类.赋予类一个二进制名称,一个类加载器应当尝试定位
或生成
数据,这些数据构成类的定义.一种典型的策略是将二进制名称转换为文件名,而后从文件系统中读取该名称的字节码文件
。
每个类
对象都包含定义该类
的classLoader引用(reference)
数组
对应的class对象并非由类加载器建立的,而是由java虚拟机在须要时自动建立的.对于一个数组的类加载器,与这个数组元素的类加载器一致.若是数组是原生类型
,那这个数组将没有classLoader
String[],则这个数组的类加载器是String的类加载器,使用的是Bootstrap类加载器 int[] ,这种基本类型的数组,是没有类加载器的.
应用
实现classLoader的目的是为了拓展JVM动态加载类
ClassLoader使用了委托模型去寻找类的资源.ClassLoader的每个实例都有会一个关联的父ClassLoader,当须要寻找一个类的资源时,ClassLoader实例就会委托给父ClassLoader.虚拟机内建的ClassLoader称为BootstrapClassLoader
,BootstrapClassLoader
自己是没有父ClassLoader的,可是能够做为其余ClassLoader的父加载器
支持并发加载的类加载器称为并行类加载器,这种类加载器要求在类初始化期间经过ClassLoader.registerAsParallelCapable
将自身注册上.默认状况下就是并行的,而子类须要须要并行,则须要调用该方法
在委托机制并非严格层次化的环境下,classLoader须要并行处理,不然类在加载过程当中会致使死锁,由于类加载过程当中是持有锁的
一般状况下,JVM会从本地的文件系统中加载类,这种加载与平台相关.例如在UNIX系统中,jvm会从环境变量中CLASSPATH定义的目录中加载类.
然而有些类并非文件,例如网络,或者由应用构建出来(动态代理),这种状况下,defineClass
方法会将字节数组转换为Class实例,能够经过Class.newInstance
建立类真正的对象 由类加载器建立的对象的构造方法和方法,可能会引用其余的类,因此JVM会调用loadClass
方法加载其余引用的类
二进制名称BinaryNames
,做为ClassLoader中方法的String参数提供的任何类名称,都必须是Java语言规范所定义的二进制名称。 例如
步骤
loadClass
方法loadClass
方法中实现加载class字节码的方法,返回byte[]super.defineClass(byte[])
方法将Class对象返回给loadClass方法源码示例
public class Test16 extends ClassLoader {
private String classLoaderName;
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
//将systemClassLoader做为当前加载器的父加载器
super();
this.classLoaderName = classLoaderName;
}
public Test16(ClassLoader parent, String classLoaderName) {
//将自定义的ClassLoader做为当前加载器的父加载器
super(parent);
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public static void main(String[] args) throws Exception {
Test16 loader1 = new Test16("loader1");
//设置绝对路径,加载工程根目录下的com.r09er.jvm.classloader.Test01.class
loader1.setPath("/Users/cicinnus/Documents/sources/jvm-learning/");
Class<?> aClass = loader1.loadClass("com.r09er.jvm.classloader.Test01");
//打印加载的类
System.out.println("loader1 load class" + aClass.hashCode());
Object instance = aClass.newInstance();
System.out.println("instance1: " + instance);
Test16 loader2 = new Test16("loader2");
//设置绝对路径,加载工程根目录下的Test01.class
loader2.setPath("/Users/cicinnus/Documents/sources/jvm-learning/");
Class<?> aClass2 = loader2.loadClass("com.r09er.jvm.classloader.Test01");
System.out.println("loader2 load class" + aClass2.hashCode());
Object instance2 = aClass2.newInstance();
System.out.println("instance2 : " + instance2);
//todo ****
//1.从新编译工程,确保默认的classPath目录下有Test01.class的字节码文件,而后运行main方法,观察输出
//2.删除默认classpath目录下的Test01.class,运行main方法,观察输出
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("invoke findClass");
System.out.println("class loader name : " + this.classLoaderName);
byte[] bytes = this.loadClassData(name);
return super.defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassData(String binaryName) {
byte[] data = null;
binaryName = binaryName.replace(".", "/");
try (
InputStream ins = new FileInputStream(new File(this.path + binaryName + this.fileExtension));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
) {
int ch;
while (-1 != (ch = ins.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
复制代码
执行两次main方法后,会发现类加载器真正生效的逻辑,由于默认的父加载器实际上是系统加载器(AppClassLoader
),因此若是默认的classPath存在字节码文件,则会由AppClassLoader
正确加载类,若是classPath中没有,则会向下使用自定义的类加载器加载类
若是构造函数传入两个不同的ClassLoaderName,会发现两个class对象并不一致,是因为命名空间NameSpace
的缘由,由于两个类加载器定义的名称是不同的,若是改为相同的名称,则两个class对象一致
重写的是findClass方法,在调用时候,使用的是classLoader的
loadClass
方法,这个方法内部会调用findClass
还有一个重点,若是将class字节码文件放在根目录,则会抛出
NoClassDefFoundError
异常,由于binaryName
不符合规范.
实现本身的类加载器,最重要就是实现findClass,经过传入binaryName
,将二进制名称加载成一个Class对象
在实现findClass
后,须要经过defineClass方法,将二进制数据交给defineClass
方法转换成一个Class实例, 在defineClass
内部会作一些保护和检验工做.
经过loadClass
方法加载类,会有以下默认加载顺序
findLoadedClass
方法检查class是否被加载loadClass
方法,若是父加载器为null,则会调用JVM内建的类加载器.findClass
方法找到类在默认的loadClass方法中,类加载是同步
的
双亲委派机制优势
同一个类(包名,类名一致)
,命名空间发挥的做用
由于优先加载的是类库中的class,会忽略掉自定义的类
当类被加载,链接,初始化以后,它的生命周期就开始了.当表明类的Class对象再也不被引用,即不可触及时,Class对象就会结束生命周期,类在元空间内的数据也会被卸载,从而结束类的生命周期.
一个类什么时候结束生命周期,取决于表明它的Class对象什么时候结束生命周期
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载.
用户自定义的类加载器所加载的类是能够被卸载的
BootstrapClassLoader加载的路径
ExtClassLoader
AppClassLoader
三个路径和JDK版本,操做系统都有关系
若是将编译好的class字节码文件放到根加载器的加载路径上,能够成功由BootstrapClassLoader加载
即子加载器能访问父加载器加载的类,而父加载器不能访问子加载器加载的类.(相似于继承的概念)
一个Java类是由该类的全限定名称+用于加载该类的定义类加载器(defining loader)共同决定.
ClassLoader.getSystemClassLoader
源码返回用于委托的系统类加载器.是自定义类加载器的父加载器,一般状况下类会被系统类加载器加载. 该方法在程序运很早的时间就会被建立,而且会将系统类加载器设为调用线程的上下文类加载器(context class loader
)
1.初始化ExtClassLoader 2.初始化AppClassLoader,将初始化好的ExtClassLoader设置为AppClassLoader的父加载器 3.将AppClassLoader设置为当前线程的上下文类加载器
SystemClassLoaderAction
逻辑1.判断System.getProperty("java.system.class.loader")
是否有设置系统类加载器 2.若是为空,直接返回AppClassLoader
3.若是不为空,经过反射建立classLoader,其中必须提供一个函数签名为ClassLoader
的构造 4.将反射建立的自定义类加载器设置为上限为加载器. 5.返回建立好的类加载器
Class.ForName(name,initialize,classloader)
解析name
,须要构造的类全限定名称(binaryName)不能用于原生类型或者void类型 若是表示的是数组,则会加载数组中的元素class对象,可是不进行初始化
initialize
,类是否须要初始化classloader
,加载此类的类加载器ContextClassLoader
)实现与分析CurrentClassLoader(当前类加载器)
ClassLoader
去加载当前类引用的其余类若是ClassA引用了ClassY,那么ClassA的类加载器会去加载ClassY,前提是ClassY未被加载
线程类加载器从JDK1.2开始引入,Thread类中的getContextClassLoader
和setContextClassLoader
分别用来获取和设置上下文加载器.若是没有手动进行设置,那么线程会继承其父线程的上下文加载器. java应用运行时的初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的类能够经过这个类加载器加载类与资源
回顾一下JDBC操做
Class.forName("com.mysql.driver.Driver");
Connection conn = Driver.connect();
Statement stae = conn.getStatement();
复制代码
Driver
,Connection
,Statement
都是由JDK提供的标准,而实现是由具体的DB厂商提供. 根据类加载的机制,JDK的rt包会被BootstrapClassLoader
加载,而自定义的类会被AppClassLoader
加载,同时由于命名空间
的缘由,父加载器是没法访问子加载器加载的类的.因此父加载器会致使这个问题.
上下文加载器就是为了解决这种问题所存在的
父ClassLaoder可使用当前线程Thread.currentThread().getContextClassLoader()
加载的类, 这就改变了父ClassLoader不能使用子ClassLoader或是其余没有直接父子关系的ClassLoader没法访问对方加载的class问题.
即改变了父亲委托模型
使用步骤(获取 - 使用 - 还原)
ContextClassLoader的做用就是破坏Java的类加载委托机制
ServiceLoader
是一个简单的服务提供者加载设施
加载基于JDK规范接口实现的具体实现类 实现类须要提供无参构造,用于反射构造出示例对象
服务提供者将配置文件放到资源目录的META-INF/services
目录下,告诉JDK在此目录的文件内配置了须要加载的类,其中文件名称是须要加载的接口全限定名称,文件内容是一个或多个实现的类全限定名称.
在双亲委托模型下,类加载时由下至上的.可是对于SPI
机制来讲,有些接口是由Java核心库提供的,根据类加载的机制,JDK的rt包会被BootstrapClassLoader
加载,而自定义的类会被AppClassLoader
加载.这样传统的双亲委托模型就不能知足SPI
的状况,就能够经过线程上下文加载器来实现对于接口实现类的加载.