将字节码文件加载到 jvm,并创建对应的字节码对象,然后对其进行验证、初始化等操作
共 5 个阶段:加载、验证、准备、解析、初始化,这里只记录【加载】里的类加载器和双亲委派
加载的是字节码文件
通过类加载器 ClassLoader 把字节码文件在堆中生成代表这个 class 文件的 java.lang.Class 对象
ClassLoader 分类
- 站在虚拟机角度分为两种:启动类加载器(c++实现,虚拟机的一部分)、其他类加载器(java实现,java.lang.ClassLoader 的字类)
- 站在开发角度可以分为 4 种启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器
各种 ClassLoader
- 启动类加载器(Bootstrap ClassLoader)
- 加载 java 核心类库,JAVA_HOME/lib 目录下的类库或 -Xbootclasspath 参数指定路径的类库
- 处于安全考虑只加载 java、javax、sun 开头的类
- 没有父加载器
- 扩展类加载器(Extension ClassLoader)
- 加载 JAVA_HOME/lib/ext 目录中的类库
- 父加载器是 Boostrap ClassLoader
- 应用程序类加载器(Application ClassLoader)
- 加载我们自己编写的类
- 父加载器是 Extention ClassLoader
- 自定义类加载器(Customer ClassLoader)
- 继承 java.net.URLClassLoader 或 java.lang.ClassLoader类,重写 findClass,如果要打破双亲委派机制需要重写 loadlass 方法
- 加载自己指定路径的class文件
- 父加载器是 Application ClassLoader
双亲委派
- 目的是保证类的唯一性和安全性
- 比如我们自定义一个 java.lang 包,然后创建 String 类,jdk 本身也存在 java.lang.String 类,显然是有问题的
- 具体是:加载器收到一个类的加载请求,不管自己能不能加载都让父类加载器加载,直到引导类加载器
- 如果引导类加载器加载不了,扩展类加载器加载;如果还是加载不了,应用类加载器尝试加载;如果还加载不了,说明没这个类抛出 ClassNotFound 异常或让自定义类加载器加载
类加载方式
- 隐式加载
- 遇到 new,或者获取静态变量等时
- 对类进行反射调用时
- 虚拟机启动时,加载包含 main 方法的主类
- 显式加载
- Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
- ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块
- Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象
自定义类加载器
除了BootstrapClassLoader是由C/C++实现的,其他的类加载器都是ClassLoader的子类。所以如果我们想实现自定义的类加载器,首先要继承ClassLoader
- 如果不想打破双亲委派机制,那么只需要重写findClass方法
- 如果想要打破双亲委派机制,那么就需要重写整个loadClass方法
自定义类加载器
public class MyClassLoader extends ClassLoader{
//默认ApplicationClassLoader为父类加载器
public MyClassLoader(){
super();
}
//加载类的路径
private String path = "";
//重写findClass,调用defineClass,将代表类的字节码数组转换为Class对象
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] dataByte = new byte[0];
try {
dataByte = ClassDataByByte(name);
} catch (IOException e) {
e.printStackTrace();
}
return this.defineClass(name, dataByte, 0, dataByte.length);
}
//读取Class文件作为二进制流放入byte数组, findClass内部需要加载字节码文件的byte数组
private byte[] ClassDataByByte(String name) throws IOException {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
name = name.replace(".", "/"); // 为了定位class文件的位置,将包名的.替换为/
is = new FileInputStream(new File(path + name + ".class"));
int c = 0;
while (-1 != (c = is.read())) { //读取class文件,并写入byte数组输出流
arrayOutputStream.write(c);
}
data = arrayOutputStream.toByteArray(); //将输出流中的字节码转换为byte数组
is.close();
arrayOutputStream.close();
return data;
}
}
使用自定义类加载器加载指定的类
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("com.fengjian.www.MyClassLoader");
clazz.newInstance();
}
标签:java,name,自定义,ClassLoader,jvm,byte,加载
From: https://www.cnblogs.com/hangychn/p/17317541.html