JAVA的类加载器
Class文件的认识
大家都知道Java中程序是运行在虚拟机中的,我们平常用文本编辑器或者IDE编写的程序都是.java格式的文件,这是最基础的源代码,但是java虚拟机并不能直接识别,所以需要转换成.class文件,.class文件是字节码格式文件。
JAVA类加载流程
JAVA系统自带有三个类加载器:
BootstrapClassLoader 最顶层的加载类,主要加载核心库,%JRE_HOME%\lib下的rt.jar、resources.jar和class等。
ExtentionClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载系统变量 java.ext.dirs选项制定的目录。
AppClassLoader 加载当前应用的classpath的所有类。
系统的3个类加载顺序是依次加载的。
jdk1.9及其以后版本发生了改变!
sun.misc.Launcher中的AppClassLoader改成了jdk.internal.loader.ClassLoaders中的AppClassLoader
sun.misc.Launcher中的ExtClassLoader改成了jdk.internal.loader.ClassLoaders中的PlatformClassLoader
以下代码均运行于jdk1.8版本!
类加载器和他们的父加载器
我们写一个测试类并执行
package cn.m3.test.client;
/**
* Created by ZhuHao on 2018/9/24
*/
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
while(true){
System.out.println(classLoader.getClass().getName());
classLoader = classLoader.getParent();
if(classLoader == null){
break;
}
}
System.out.println(String.class.getClassLoader());
}
}
运行后输出
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
可以看到TestClassLoader.class是由AppClassLoader加载的,而AppClassLoader的父加载器是ExtClassLoader,而ExtClassLoader的父加载器为NULL,程序break,String类的类加载器则直接返回null。
父加载器不是父类
可以查看ExtClassLoader和AppClassLoader的源代码
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
可以看到ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但为什么调用AppClassLoader的getParent()会得到ExtClassLoader的实例呢?
先看一张类的继承关系图
查看JVM的入口sun.misc.Launcher的源代码,精简后
public class Launcher {
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private static Launcher launcher = new Launcher();
public Launcher() {
Launcher.ExtClassLoader var1= Launcher.ExtClassLoader.getExtClassLoader();
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
Thread.currentThread().setContextClassLoader(this.loader);
}
}
getPanrent()方法位于ClassLoader类下,直接返回私有变量parent,那么这个parent只有在ClassLoader的构造方法中初始化。回到Launcher源码this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);,再去查看getAppClassLoader便清楚了。
那么ExtClassLoader为什么没有传入BootstrapClassLoader作为parent参数呢?
因为BootstrapClassLoader是C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在JAVA代码中获取它的引用,所以测试代码中获取String类型的加载器就会返回null。
双亲委托
JVM加载一个class时先查看是否已经加载过,没有则通过父加载器,然后递归下去,直到BootstrapClassLoader,如果BootstrapClassloader找到了,直接返回,如果没有找到,则一级一级返回(查看规定加载路径),最后到达自身去查找这些对象。这种机制就叫做双亲委托。
好处是:
1.避免重复加载
A和B都需要加载X,各自加载就会导致X加载了两次,JVM中出现两份X的字节码;
2.防止恶意加载
编写恶意类java.lang.Objcet,自定义加载替换系统原生类;
按需查看ClassLoader源码下的loadClass方法(精简)
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
}
也很好的印证了双亲委托模型,先从缓存找,然后根据parent是否为null(BoostrapClassLoader)向上委托加载。
双亲委托是JVM的规范,是可以通过在自定义ClassLoader时重写loadClass方法打破的,而JDK1.2之后不建议直接重写loadClass,不想打破规范只需要重写findClass方法即可。
自定义类加载器
先编写一个测试类
package cn.m3.test.client;
/**
* Created by ZhuHao on 2018/9/25
*/
public class HelloDemo {
@Override
public String toString() {
return "Hello World";
}
}
编译后将.class文件放到C盘根目录下
可以继承ClassLoader实现自定义类加载器
1.编写一个类继承ClassLoader。
2.重写它的findClass()方法。
3.在findClass()方法中调用defineClass()。
更简单的方法是继承URLClassLoader
package cn.m3.test.client;
import java.io.File;
import java.net.*;
/**
* Created by ZhuHao on 2018/9/25
*/
public class MyClassLoader extends URLClassLoader {
public MyClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public MyClassLoader(URL[] urls) {
super(urls);
}
public MyClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(urls, parent, factory);
}
public static void main(String[] args) throws ClassNotFoundException,MalformedURLException,URISyntaxException{
String dir = "C:/";
File file = new File(dir);
URI uri = file.toURI();
URL[] urls = {uri.toURL()};
MyClassLoader loader = new MyClassLoader(urls);
try{
Class<?> object = loader.loadClass("cn.m3.test.client.HelloDemo");
System.out.println(object.newInstance().toString());
}catch (Exception e){
e.printStackTrace();
}
}
}
只需要重写构造器,连findClass()方法都无需编写,运行后控制台输出了"Hello World"。