文章目录
- 前言
- 一、JVM中类的加载过程?
- 二、类加载器的分类
- 三、什么是类加载器的双亲委派机制
一、JVM中类的加载过程
类的加载过程是指将编译后的字节码文件加载到我们的JVM虚拟机中。整个加载过程几个重要的步骤分为如下:
- 加载。(这里的加载是指通过IO流读取我们编译后的磁盘上的字节码文件,即class文件)
- 验证。(验证读取的字节码文件是否为可识别的标准的字节码文件)
- 准备。(为类的成员变量分配空间,并且赋予默认值)
- 解析。(将符号引用替换为直接引用,如类中有main这种static静态方法,会将引用直接指向所存内存的指针)
- 初始化。(初始化变量,对类的成员变量进行初始化,并且执行类的静态代码块)
二、类加载器分类
JVM中有很多种类型的类加载器。其中我们需要关注的有三种类加载器:
- 引导类加载器(最顶层,负责加载jre下的lib包。该加载器由c++实现,jdk中无法获取该类加载器的实例,如果打印该实例,会为null。该类实例化过程中,会创建扩展类加载器并且指定扩展类加载器的父加载器为引导类加载器;实例化应用类加载器,指定父类加载器为扩展类加载器。)注意:这里的父类加载器并不是指继承,而是指定其中的parent属性。该属性与下面要讲的类加载器的双亲委派机制有关。
- 扩展类加载器(设为加载ext目录下所有的jar包)
- 应用类加载器(负责加载classpath下的class类)
还有我们可以自定义类加载器,可以自己指定加载目录。只要继承我们的classloader类,并且重写其中的findclass方法即可,如下:
package com.transfar.nio;
import java.io.FileInputStream;
import java.lang.reflect.Method;
class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name); //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String args[]) throws Exception { //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test"); //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.test.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
}
三、类加载器的双亲委派机制
- 什么叫类加载器的双亲委派机制
类的双亲委派机制是指在加载类的过程中,子类加载器在加载class文件时会先判断该类是否已经加载过,如果加载过则直接返回,如果没有加载,会请求它的父类加载器去加载,即:AppclassLoader加载类时,如果该类没有加载过,会委托它的父类加载器(extClassLoader)去加载。extClassLoader又会执行相同的逻辑,会委托给引导类加载器去加载。上面讲过,引导类加载器主要是加载jre下的lib包,如果它无法完成加载,则将会一级一级返回,还是由子类加载器完成类的加载。过程如下图。
为什么需要设计双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
-
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
第一点如何理解呢。比如如果我们定义一个String.class。将其放在一个java.lang包下。然后我们去加载类的时候会发现是无法加载到这个我们自己定义的string.class类。因为引导类加载器会加载rt.jar包下的jdk的String类。会直接返回这个类的实例。第二点比较好理解,就是指加载过的类不会再次加载而是直接返回该类。