少女祈祷中...

1.类加载机制

类加载器ClassLoader

  • 负责查找、加载、校验字节码的应用程序
  • java.lang.ClassLoader
    • load(String className) 根据名字加载一个类,返回类的实例
    • defineClass(String name, byte[] b, int off, int len) 将一个字节流定义一个类
    • findClass(String name) 查找一个类
    • findLoadedClass(String name) 在已加载的类中,查找一个类
    • 成员变量 ClassLoader parent

JVM四级类加载器

  • 启动类加载器(Bootstrap),系统类rt.jar
  • 扩展类加载器(Extension),jre/lib/ext
  • 应用类加载器(App),classpath
  • 用户自定义加载器(Plugin),程序自定义

类加载器双亲委托

  • 首先判断是否已经加载
  • 若无,找父加载器加载
  • 若再无,由当前加载器加载

2.双亲委托加载扩展

  • Java严格执行双亲委托机制
    • 类会由最顶层的加载器来加载,如没有,才由下级加载器加载
    • 委托是单向的,确保上层核心的类的正确性
    • 但是上级类加载器所加载的类,无法访问下级类加载器所加载的类
      • 例如,java.lang.String 无法访问自定义的一个Test类
      • Java是一个遵循契约设计的程序语言,核心类库提供接口,应用层提供实现
      • 核心类库是BootstrapClassLoader加载
      • 应用层是AppClassLoader加载
      • 典型例子是JDBC和XML Parser等

双亲委托的补充

  • 执行Java,添加虚拟机参数-Xbootclasspath/a:path,将类路径配置为Bootstrap等级
  • 使用ServiceLoader.load方法,来加载(底层加载器所加载的类)

ServiceLoader

  • JDK 6引入的一种新特性,是用于加载服务的一种工具
  • 服务有接口定义和具体的实现类(服务提供者)
  • SPI机制,Service Provider Interface
  • 一个服务提供者会在jar包中有META-INF/services目录,里面放一个文件,名字同接口名字。内容的每一行都是接口的一个实现类
  • load方法,可以用当前线程的类加载器来获取某接口的所有实现,当然也都是转为接口类来使用
  • 注意:此服务和Java 9模块系统的服务略有差别,但是都能通过ServiceLoader进行加载

3.自定义类加载路径

自定义类加载器

  • 自定义加载路径
    • 弥补类搜索路径静态的不足
    • URLClassLoader, 从多个URL(jar或者目录)中加载类
  • 自定义类加载器
    • 继承ClassLoader类
    • 重写findClass(String className)方法

自定义加载路径

  • 弥补类搜索路径静态的不足
  • 前3个加载的路径都是运行前确定的
  • Java提供URLClassLoader
    • 程序运行时修改类的加载路径
    • 可从多个来源中加载类

URLClassLoader

  • 继承于ClassLoader
  • 程序运行时增加新的类加载路径
  • 可从多个来源中加载类
    • 目录
    • jar包
    • 网络
  • addURL添加路径
  • close方法关闭
1
2
3
4
5
6
7
8
9
10
11
//URL支持http, https, file, jar 四种协议
URL url = new URL("file:E:/java/source/PMOOC11-03-First/bin/");
//URL url = new URL("file:C:/Users/Tom/Desktop/PMOOC11-03-First.jar");

//程序运行时,添加一个classpath路径
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> c = loader.loadClass("edu.ecnu.Hello");

//采用反射调用
Method m = c.getMethod("say");
m.invoke(c.newInstance());

4.自定义类加载器

  • 继承ClassLoader类
  • 重写findClass(String className)方法
  • 使用时,默认先调用loadClass(className)来查看是否已经加载过,然后委托双亲加载,如果都没有,再通过findClass加载返回
    • 在findClass中,首先读取字节码文件
    • 然后,调用defineClass(className, bytes, off, len) 将类注册到虚拟机中
    • 可以重写loadClass方法来突破双亲加载
  • 同一个类可以被不同层级的加载器加载,且作为2个类对待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class CryptoClassLoader extends ClassLoader 
{
private int key = 3;

public Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
byte[] classBytes = null;
//读取Hello.caesar文件,得到所有字节流
classBytes = loadClassBytes(name);
//调用defineClass方法产生一个类,并在VM中注册
Class<?> cl = defineClass(name, classBytes, 0, classBytes.length);
if (cl == null) throw new ClassNotFoundException(name);
return cl;
}
catch (IOException e)
{
throw new ClassNotFoundException(name);
}
}

/**
* Loads and decrypt the class file bytes.
* @param name the class name
* @return an array with the class file bytes
*/
private byte[] loadClassBytes(String name) throws IOException
{
String cname = "E:/java/source/PMOOC11-04/bin/edu/ecnu/Hello.caesar";
byte[] bytes = Files.readAllBytes(Paths.get(cname));
for (int i = 0; i < bytes.length; i++)
bytes[i] = (byte) (bytes[i] - key);
return bytes;
}
}