一、类加载全过程
JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。
- 加载,将class字节码文件加载到内存中,并将这些静态数据装换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.class对象,作为方法区数据的访问入口。
- 链接,将Java文件的二进制代码合并到JVM的运行状态的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
- 准备:正式为类变量(static变量)分配内存并设置类变量初始值的过程,这些内存都将在方法区中分配。
- 解析:虚拟机常量池内的符号引用替换为直接引用的过程。
- 初始化,初始化阶段是执行类构造器方法的过程,类构造器方法是由编译器自动收集类中的所有的类变量的赋值动作和静态语句块(static修饰的代码块)中的语句合成的。如果初始化一个类如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化,虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
二、类加载器的作用
- 类加载器的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
- 类缓存:标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。
- java.class.ClassLoader: java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java 类,即 java.lang.Class类的一个实例。– 除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
相关方法:
– getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实
例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是
java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。
– 对于以上给出的方法,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如
com.example.Sample$1和com.example.Sample$Inner等表示方式。
三、类加载器的层次结构
- 引导类加载器(bootstrap class loader),它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的,并不继承自 java.lang.ClassLoader。– 加载扩展类和应用程序类加载器。并指定他们的父类加载器。
- 扩展类加载器(extensions class loader),用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。– 由sun.misc.Launcher$ExtClassLoader实现。
- 应用程序类加载器(application class loader),它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。– 由sun.misc.Launcher$AppClassLoader实现。
- 自定义类加载器,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
四、类加载器的代理模式
- 代理模式
– 交给其他加载器来加载指定的类
- 双亲委托机制
– 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
– 双亲委托机制是为了保证 Java 核心库的类型安全。 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。
– 类加载器除了用于加载类,也是安全的最基本的屏障。
- 双亲委托机制是代理模式的一种
– 并不是所有的类加载器都采用双亲委托机制。
扫描二维码关注公众号,回复:
6782541 查看本文章
– tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。
这与一般类加载器的顺序是相反的。
java.class.ClassLoader 类API:
– getParent() 返回该类加载器的父类加载器。
– loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
• 此方法负责加载指定名字的类,首先会从已加载的类中去寻找,如果没有找到;从parent ClassLoader[ExtClassLoader]中加载;如
果没有加载到,则从Bootstrap ClassLoader中尝试加载(findBootstrapClassOrNull方法), 如果还是加载失败,则自己加载。如果还
不能加载,则抛出异常ClassNotFoundException。
• 如果要改变类的加载顺序可以覆盖此方法;
– findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
– findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实
例。
– defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是
java.lang.Class类的实例。这个方法被声明为 final的。
– resolveClass(Class<?> c) 链接指定的 Java 类。
表示类名称的 name参数的值是类的名称。需要注意的是内部类的表示,如 com.example.Sample$1和
com.example.Sample$Inner等表示方式。
五、自定义类加载器
package com.mqc.loader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author maoqichuan
* @ClassName: FileSystemClassLoader
* @description: 自定义文件系统类加载器
* @date 2019-06-1717:31
**/
public class FileSystemClassLoader extends ClassLoader {
/**
* 从指定目录中加载class文件
*/
private String loaderDir;
public FileSystemClassLoader(String loaderDir){
this.loaderDir = loaderDir;
}
/**
* @description: 重写类加载方法加载class文件
* @author maoqichuan
* @date 2019-06-17 17:32
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = findLoadedClass(name);
//应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
if (clz != null){
return clz;
}else{
try {
ClassLoader parent = this.getParent();
// 通过代码控制实现代理模式的双亲委派机制,但是记得需要捕获异常
try {
clz = parent.loadClass(name);
}catch (Exception e){
e.printStackTrace();
}
if (clz != null){
return clz;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
clz = defineClass(name, classData, 0,classData.length);
}
}
}catch (Exception e){
}
}
return clz;
}
/**
* @description: 获取class文件的字节数据
* @author maoqichuan
* @date 2019-06-17 17:37
*/
private byte[] getClassData(String classname) {
String path = loaderDir +"/"+ classname.replace('.', '/')+".class";
// IOUtils,可以使用它将流中的数据转成字节数组
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp=0;
while((temp=is.read(buffer))!=-1){
baos.write(buffer, 0, temp);
}
return baos.toByteArray();
}catch(Exception e){
e.printStackTrace();
return null;
}finally{
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上面这个是我自定义的文件系统类加载器,其实我们可以改造成网络类加载器,还有加密解密类加载器。