`

Java的类加载器ClassLoader

    博客分类:
  • jvm
阅读更多

前言

 

ClassLoader java的类加载器,其作用就是把编译好的class文件或者jar包中对应的类的元数据加载到jvm的方法区,在堆中创建一个Class对象并返回,调用这个这个Class对象的newInstance()方法就可以创建一个指定类对应的对象。熟悉反射的朋友应该很清楚,如果再在前面加一段通过Class.forName()方法来获取Class对象的话,这其实就是java“反射获取对象的实现过程。

 

Class.forName()方法会调用forName0方法,这是一个jvm实现的native方法,里有一个重要的参数就是ClassLoader对象。Class.forName()方法是显示的获取一个Class对象的方法,jvm会调用根据参数里的ClassLoader对象来查询并加载一个Class对象。

 

Class.forName()方法是一种显示的获取Class对象的方法。这种方式一般是通过反射来创建java对象。我们平时用得更多的是使用new关键字来创建java对象,这个过程一个隐式创建java对象的过程:首先jvm会找到当前上下文的ClassLoader对象,通过这个ClassLoader对象来Load并创建一个Class对象;再通过Class对象的newInstance()方法实例化一个类对象。这个过程完全就jvm自己完成,所以称为隐式加载。通过这个过程,我们还可以发现,jvm是按需加载的,也就是说在new关键字被调用后,才会去加载对应的Class对象。否则如果一上来就把所有的jar包都加载到内存,势必一种内存空间的浪费;同时程序启动时间也会变长。

 

java自带的ClassLoader对象的创建过程

 

提示:本文中贴出的源码都是基于JDK1.8

ClassLoaderjava中定义的是一个抽象类,也说真实的ClassLoader对象是其子类对象。ClassLoader类中定义了一些通用的方法,比如需要一个ClassLoader时可以通过getSystemClassLoader()方法:

public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader(); //初始化ClassLoader对象
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {//权限检查
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
 

 

这个方法没什么好说的关键就是调用initSystemClassLoader()方法 对ClassLoader对象进行初始化。initSystemClassLoader这方法不多说,其中核心部分是创建了一个Launcher对象:

sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

 

java自带的类加载器对象就是在Launcher类中创建的。下面我们主要来看下Launcher类,这个类从整体上来看运用了一个 “类单例模式”,大致结构如下:

public class Launcher {
    private static Launcher launcher = new Launcher();
    private ClassLoader loader;
    //省略其他成员变量
 
    public static sun.misc.Launcher getLauncher() {
        return launcher;
    }
    //公有的构造方法,与典型的单例模式有点差别
    public Launcher() {
        //省略方法体
}
//省略其他方法
}
 

 

这是一种典型的饿汉式单例模式(除了构造方法是public),在ClassLoadinitSystemClassLoader()方法中就是通过getLauncher()方法来获取Launcher对象的,可以看到通过这个方法获取的对象,每次都是同一个。

 

Launcher的构造方法做了三件事:创建ExtClassLoader类加载器对象;创建AppClassLoader类加载器对象;创建SecurityManager安全策略对象。关于java的安全策略这里就不展开了,感兴趣的可以异步到这里https://www.cnblogs.com/yiwangzhibujian/p/6207212.html。下面是该构造方法源码:

 

public Launcher() {
        //创建ExtClassLoader类加载器对象
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
 
        //创建AppClassLoader类加载器对象
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //把AppClassLoader类加载器对象设置到上线文
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
       
        //创建SecurityManager安全策略
        if(var2 != null) {
            SecurityManager var3 = null;
            if(!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }
 
            if(var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }
 
            System.setSecurityManager(var3);
        }
 
    }
 

 

可以看到ExtClassLoaderAppClassLoader是二者都是Launcher的内部类,并且都继承自URLClassLoader;而URLClassLoader又都继承自前面提到的抽象类ClassLoader。二者的区别是AppClassLoader的父加载器是ExtClassLoader,而ExtClassLoader的父加载器为空(AppClassLoader的构造方法需要一个ExtClassLoader对象)。

 

Launcher的构造方法首次执行后,Launcher的单例对象就创建完成。下面继续返回ClassLoader抽象类。

 

当需要加载一个Class对象时:

1、首先通过ClassLoadergetSystemClassLoader()方法获取到到当前的ClassLoader对象(AppClassLoader对象);

2、然后调用该ClassLoader对象的LoadClass方法loadClass()方法进行加载。

下面就来看LoadClass方法的实现流程:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查该Class对象是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果有父加载器就交给父加载器加载
                    //否则就交给BootstrapClassLoader 加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果父加载器 以及BootstrapClassLoader加载器都没有加载,就自己加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //自己加载,如果加载不到就抛出ClassNotFoundException异常。
                    //该方式收抽象方法,交给子类实现
                    c = findClass(name);
 
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
 

 

LoadClass方法的核心流程如下:

1、首先通过findLoadedClass方法,检查自己是否已经加载过该class对象。最终调用的是native方法findLoadedClass0进行检查,由于是jvm实现的看不到源码。可以猜测到已经该类加载器 加载过的class对象,应该是被缓存起来了。这里还可以猜测到“类加载器”和class对象之前相互持有对方的引用,这也就是为什么class对象 以及方法区很难被“垃圾回收”的原因,除非首先想办法回收掉“类加载器”或者切断二者之间的引用关系(要自己去实现很复杂,OSGI在回收Bundle时,应该就是这么做的)。

 

2、类加载器,首先不自己加载,而是交给自己的父类加载器加载。从前面的讲解我们可以得知getSystemClassLoader()方法获取的真实的ClassLoaderAppClassLoader,其父类加载器是ExtClassLoader;父加载器ExtClassLoader又会重复步骤1检查自己是否已经加载过,否则到步骤2,此时ExtClassLoader的父加载器为空,就会交给BootstrapClassLoader加载器加载。

 

3、如果步骤2中,父加载器ExtClassLoader、以及BootstrapClassLoader加载器都没有加载,则自己调用findClass方法进行加载。

 

在上述三个步骤中提到的三个类加载器:BootstrapClassLoaderExtClassLoaderAppClassLoader,下一节再详细说明这三者的关系。先来看下findClass方法,在ClassLoader中是抽象方法,在UrlClassLoader中进行了实现,内容就比粘贴了,大致就是URI路径找到class文件,然后调用自己defineClass方法进行取去文件内容到ByteBuffer中,最后再调用父类ClassLoader中的defineClass方法进行加载,该方式是native的,由jvm实现。简易序列图如下:



 

简单的理解就是 loadClass方法-->调用findClass方法-->调用defineClass方法

 

findClass方法一般交由子类子类字节去实现从哪里找“class”文件,UrlClassLoader的实现是在一个目录下找。当然也可以在网络中去找,只要把文件内容读取到ByteBuffer中就行。

 

至此关于类加载器如何加载一个Class对象的流程就分析完毕。

 

java中自带的三个类加载器

 

在上一节中,可以看到java中自带的三个类加载器:BootstrapClassLoaderExtClassLoaderAppClassLoader。准确的说BootstrapClassLoader应该是JVM自带的类加载器,ExtClassLoaderAppClassLoaderjdk api中定义的类加载器,他们都是ClassLoader的间接子类,并且是Launcher的内部类。

 

三个类加载器的最大区别就是 他们各自加载类的路径不同,也就是我们常说的ClassPath。它们的类路径在Launcher类中都可以看到:

 

BootstrapClassLoader:读取系统配置sun.boot.class.path,默认配置路径为:System.out.println(System.getProperty("sun.boot.class.path"));

 

ExtClassLoader:读取系统配置java.ext.dirs,默认配置路径为:System.out.println(System.getProperty("java.ext.dirs"));

 

AppClassLoader:读取系统配置java.class.path,默认配置路径为:System.out.println(System.getProperty("java.class.path"));

 

执行上面的System.out.println就可以看到你电脑中,三个类加载器的加载路径。简单的理解:

BootstrapClassLoader加载的是java的核心包,比如rt.jar

ExtClassLoader加载的是扩展包 $JAVA_HOME/ lib/ext

AppClassLoader加载的是自己应用开发的jar包(或者class文件)。

 

java 类加载器的父优先原则

 

从第二节中,我们还可以看到java类加载器的父优先原则 即:在需要加载一个Class对象时,AppClassLoader判断自己是否加载过,如果没有,自己先不加载,而是交给ExtClassLoaderExtClassLoader判断自己是否加载过,如果没有就交给BootstrapClassLoader进行加载。

 

反过来,如果BootstrapClassLoader发现这个class文件不在自己的类路径下,就交给ExtClassLoaderExtClassLoader尝试在自己的类路径下找,如果还是没有找到,就交给AppClassLoaderAppClassLoader尝试在自己的类路径下找,如果还是没有找到,就抛出ClassNotFoundException。在上述三步中的任意一步中 如果找到,就由该类加载器加载,并被缓存起来。这就是java 类加载器的父优先原则(也有称做:双亲委派模型)。

 

为什么java的类加载要采用父优先原则呢?其中一个最重要的目的就是为了防止我们重新java中定义的类。比如:你在自己的项目中定义了一个java.lang.String类,在运行时是永远都加载不到你自己这个类的,因为每次加载时都由BootstrapClassLoader类加载器 加载自己类路径下的java.lang.String类。

 

自定义类加载器

 

通过前面几节的讲解,相信都对java的类加载器有了大致的了解,如果我们想定义自己的类加载器也是件很容易的时。无非就是继承ClassLoader类,重新其findClass方法,也就是指定类加载器到哪里去读Class文件。当然还有一个跟简单的方法就是,仿照ExtClassLoaderAppClassLoader,直接继承UrlClassLoader。这里我们采用后者来展示一个最简单的示例:

public class MyClassLoader extends URLClassLoader{
    public MyClassLoader(URL[] urls,ClassLoader parent) {
        super(urls,parent);
    }
}
 
写个main方法测试下:
public static void main(String[] args) throws Exception{
        URL temp = new URL("file:///D:/test/");
        URL [] urls = {temp};
        MyClassLoader myClassLoader = new MyClassLoader(urls,MainTest.class.getClassLoader().getParent());
        Class c1 = myClassLoader.loadClass("com.sky.main.Hello");
        Object obj1 = c1.newInstance();
        System.out.println("obj1类名:"+obj1.getClass().getName());
        Method sayhello = c1.getMethod("sayHello");
        sayhello.invoke(obj1);//调用该对象的sayhello方法
 
        System.out.println("##################");
 
        Hello obj2 = new Hello();
        System.out.println("obj2类名:"+obj2.getClass().getName());
 
        if(obj1 instanceof Hello){
            System.out.println("obj1是com.sky.main.Hello类型");
        }else{
            System.out.println("obj1不是com.sky.main.Hello类型");
        }
 
    }
 

 

这里指定了自定义MyClassLoader的类路径为“D:/test/”目录,同时设定了他的父类加载器为ExtClassLoader。另外还创建了一个Hello类,这里首先使用ExtClassLoader进行显式的加载;然后是又在mian方法中创建了一个Hello类的对象,这里会使用默认的AppClassLoader再次加载Hello类。Hello类很简单:

package com.sky.main;
 
/**
 * Created by gantianxing on 2018/2/10.
 */
public class Hello {
    public void sayHello(){
        System.out.println("hello,xiaoming");
    }
}
 

 

我们把编译好的Hello.class放到“D:\test\com\sky\main\”目录下,即可运行main方法了,打印信息为:

obj1类名:com.sky.main.Hello
hello,xiaoming
##################
obj2类名:com.sky.main.Hello
obj1不是com.sky.main.Hello类型

是不是发现了一个很奇怪的问题:使用我们自定义的ClassLoader加载的obj1对象的类名是com.sky.main.Hello,但instanceof检测时却发现不是com.sky.main.Hello类型。这是因为这里分别使用了两个类加载器,加载Hello类。

 

感兴趣的朋友还可以把上面测试代码的MyClassLoader myClassLoader = new MyClassLoader(urls,MainTest.class.getClassLoader().getParent());这行代码改为:

MyClassLoader myClassLoader = new MyClassLoader(urls,MainTest.class.getClassLoader());

 

即把MyClassLoader的父类加载器设置为AppClassLoader,再运行一次,运行结果会不同。具体为什么呢?答案就是java类加载器的父优先原则,自己摸索下。

 

这里的自定义ClassLoader很简单,当然你还可以根据自己的需要重写findClass方法。

 

总结

 

本文首先讲解了什么是类加载器,以及java的类加载器是如何创建的,并对整个创建过程,以及类加载过程源码进行了分析。然后讲解了java自带类加载器的父优先原则。最后展示了如何创建自己的类加载器。

 

创建自定义类加载器的,可以在运行时实时的加载新的class对象,并且可以通过一定的手段卸载指定的class对象,从而实现class对象的热更新。但要实现对一个jar包的热更新,使用自定义ClassLoader来实现就显得非常复杂,因为你不知道哪些Class已经加载,也就不知道要卸载哪些Class对象,除非把整个ClassLoader先卸载。这是一个复杂的过程,庆幸的是OSGI已经帮我们做了这些事情,如果想实现对jar包的热更新,选择OSGI就行了。

 

 

  • 大小: 33.6 KB
0
0
分享到:
评论

相关推荐

    Java类加载器ClassLoader用法解析

    主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    深入java虚拟机(七)深入源码看java类加载器ClassLoader 1

    摘要视图订阅曹胜欢欢迎关注微信账号:java那些事:csh624366188.每天一篇java相关的文章登录 | 注册Java程序员从笨鸟到菜鸟(81)3054

    java应用程序类加载器,ClassLoader for java Application

    java应用程序类加载器(ClassLoader for java Application),类似exe4j, 方便启动java程序, 配置灵活,支持多平台选择性配置

    java类加载器实例

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...

    Java类加载器(ClassLoader)1

    如果户创建的JAR放在此录下,也会动由扩展类加载器加载.应程序类加载器(系统类加载器,Application ClassLoader)java语编写,由sun.

    JAVA ClassLoader 讲解 (类加载器)

    ClassLoader类加载器讲解,理解JAVA类加载机制

    java类加载器

    ClassLoader 三种类加载方式 Boostrap Extenxsion 以及Application ClassLoad分别适用的场景

    J2SE笔记讲解个人修订(1.1).docx

    14 JAVA类加载器CLASSLOADER 15 JAVA简单工厂模式 16 JAVA中的注解 17 JAVA 图形界面 18 JAVA多线程 19 JAVA 反射机制 20 JAVA克隆CLONE(复制) 21 JAVA 网络编程 22 JAVA 其他未归类 23 JNI概述

    ClassLoader类加载器

    ClassLoader的API使用和自定义

    掌握Java类加载器

    类加载器是Java最强大的特征之一。但是开发者常常忘记类加载组件。类加载器是在运行时负责寻找和加载类文件的类。Java允许使用不同的类加载器,甚至自定义的类加载器。类加载器从源文件(通常是.class 或 .jar文件)...

    Java类加载机制与反射-PPT

    Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射

    自定义Java类加载器demo

    自定义Java类加载器demo,自定义了一个classLoader,重写了loadClass 和findClass,注意 loadClass打破了双亲委派机制,所有的类都要在自定义的class文件中找到,而findClass遵循了双亲委派机制

    Java类加载器原理

    自己根据一些文章总结的,不知道有没有漏洞,希望大家知道,谢谢

    ClassLoader类加载机制

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...

    classloader类加载器_基于java类的加载方式详解

    下面小编就为大家带来一篇classloader类加载器_基于java类的加载方式详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    java的ClassLoader类加载器机制

    jvm运行的过程中,需要载入类,而类的加载需要类加载器,本文章提供了java的类加载器的工作原理。可以使读者更加理解jvm的运行机制。

    深入java加载器源代码

    ClassLoader,,深入java加载器,,深入java加载器源代码

    java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器

    类加载器分为根加载器(bootstrap classloader)、扩展类加载器(ext classloader)、系统类加载器(system classloader)、自定义类加载器(通常继承java.net.URLClassLoader,重写findClass()),它们的关系通常...

    类加载器文件

    JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。 SE(J2SE),standard edition,标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。

    java类加载器1

    java.lang.ClassLoader 类介绍 java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字

Global site tag (gtag.js) - Google Analytics