一个类从编译到运行完整的编译过程:
通过 javac 把 .java 文件编译成 .class文件.
ClassLoader 把 .class文件 load 到内存.
再通过 字节码解释器 或 JIT 即时编译器 把 .class 文件编译成 OS硬件 识别的机器码 在 OS 上运行。
这就是一个 完成的 类到运行完整的过程
类的加载过程分三步:
- loading (把 class 文件放到 内存)
-
linking
1.verification (校验 class 文件是否合法,是否符合 JVM 规范)
2.preparation (静态变量赋默认值)
3.resolution (把静态变量的符号转换成内存地址) - initializing(静态变量赋初始值)
类加载器和双亲委派机制
上面的类加载过程都是通过类加载器来实现的,java 有如下几种类加载器
-
引导类加载器
加载 lib/tr.jar,charset.jar 等核心jar 包,例 : java.lang.String -
扩展类加载器
加载扩展类jar包,加载 jre/lib/ext/**.jar 下面的jar包 -
应用程序类加载器
加载 classPach指定内容的jar包 -
自定义类加载器
加载自定义jai包
双亲委派 的加载过程
双亲委派发生在类加载的第一步 :loading
双亲委派是一个孩子向父亲方向,父亲向孩子方向双亲委派的一个过程。
加载 Class 的时候,先加载
Custom ClassLoader 缓存 -> 有的话直接返回,否则 加载
App ClassLoader 缓存 -> 有的话直接返回, 否则 加载
Extension ClassLoader 缓存 -> 有的话直接俄返回,否则加载
Bootstrap ClassLoader 缓存 -> 有的话直接返回,否则 加载
Bootstrap ClassLoader 类加载器 /lib/tr.jar,charset.jar 下的核心 jar 包,有的话直接返回,否则 加载子类的类加载器
Extension ClassLoader 类加载器 /jre/lib/ext/**.jar 下的jar 包,有的话直接返回,否则加载
App ClassLoader 类加载器 classPach 下的jar 包,有的话直接返回,否则加载
Cus Tom ClassLoader 类加载器 自定义的 jar 包,有的话直接返回,没有的话 抛出异常 ClassNotFountException
为什么要使用双亲委派机制?
- 沙箱安全机制:自己写的 java.lang.String.class 不会被随意加载,防止核心API 类库被随意篡改
- 避免类的重复加载:当父类已经加载了该类时,就没必要子ClassLoader再加载一次,保证被加载类的唯一性。
Java 自定义类加载的实现
自定义类加载器步骤:
- 继承ClassLoader
- 重写findClass()方法
- 调用defineClass()方法
代码实现:
public class MyClassLoader extends ClassLoader {
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
private byte[] loadByte(String className) throws IOException {
FileInputStream fis = new FileInputStream(classpath + File.separator + className.replace(".", File.separator).concat(".class"));
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
fis.close();
return bytes;
}
}
在桌面上新建一个demo文件夹,并在文件夹内创建一个Test.java文件, 然后使用 javac 命令编译 。内容如下:
public class Test {
public static void say() {
System.out.println("this is a static method!");
}
public void print(String s) {
System.out.println("printing:"+s);
}
}
测试:
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\86188\\Desktop\\demo");
Class<?> aClass = myClassLoader.loadClass("Test");
//调用的静态方法
aClass.getDeclaredMethod("say").invoke(aClass);
Object o = aClass.newInstance();
Method print = aClass.getDeclaredMethod("print", String.class);
print.invoke(o, "调用的对象方法");
System.out.println(aClass.getClassLoader());
System.out.println(aClass.getClassLoader().getParent());
System.out.println(aClass.getClassLoader().getParent().getParent());
System.out.println(aClass.getClassLoader().getParent().getParent().getParent());
}
输出结果:
this is a static method!
printing:调用的对象方法
test.MyClassLoader@677327b6
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@6d6f6e28
null