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"。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值