`

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虚拟机(七)深入源码看java类加载器ClassLoader 1

    《深入Java虚拟机(七)深入源码看java类加载器ClassLoader》 Java类加载器(ClassLoader)在Java运行环境中扮演着至关重要的角色。它负责将类的字节码加载到Java虚拟机(JVM)中,使得程序能够运行。ClassLoader是...

    Java类加载器ClassLoader用法解析

    Java 类加载器 ClassLoader 用法解析 Java 中的类加载器(ClassLoader)是一种机制,负责将类从文件系统、JAR 文件或网络等来源加载到 Java 虚拟机中。类加载器的作用是将类的二进制数据加载到内存中,并为其创建一...

    java类加载器

    ### Java 类加载器详解 #### 一、类加载器概述 在Java中,类加载器(Class Loader)是一项核心机制,用于将字节码(.class文件)加载到JVM中,使其成为运行时的对象。类加载器不仅实现了类的加载功能,还确保了...

    Java类加载器(ClassLoader)1

    Java类加载器分为三种主要类型:引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader,也称为系统类加载器)。它们共同工作,确保了Java...

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

    **java应用程序类加载器(ClassLoader for java Application)**: 当我们创建一个Java应用程序时,比如通过`java MainClass`命令启动,实际上是由应用程序类加载器来执行的。它查找并加载包含主类(Main-Class ...

    自定义Java类加载器

    2. **Extension ClassLoader**:扩展类加载器,负责加载`&lt;JAVA_HOME&gt;\lib\ext`目录下的JAR包,或者被`-Djava.ext.dirs`指定的路径中的类。 3. **System ClassLoader**:也称为应用类加载器,负责加载`CLASSPATH`...

    Java 类加载机制 ClassLoader Class.forName.pdf

    2. **Extension ClassLoader** (扩展类加载器): 负责加载扩展类库,通常位于`JAVA_HOME/lib/ext`目录下的jar包。 3. **Application ClassLoader** (应用程序类加载器): 也称为系统类加载器,负责加载用户定义的类...

    java自定义类加载classloader文档,包括代码

    上述代码展示了如何创建一个自定义类加载器`MyClassLoader`,该类继承自`java.lang.ClassLoader`。`MyClassLoader`的主要功能是从文件系统中加载指定类的二进制数据。 - **构造函数**:接受一个父类加载器和基础...

    JAVA ClassLoader 讲解 (类加载器)

    ### Java ClassLoader (类加载器)详解 #### 一、教程提示 如果你正在查看这份文档,在线版中你可以点击下面的任何主题直接跳转到相应的部分。 1. **教程提示** 2. **介绍** 3. **类加载器结构** 4. **编译类加载...

    Java类加载器原理

    Java 类加载器原理 Java 类加载器是Java虚拟机(JVM)的核心组成部分,它负责将类的字节码加载到内存中并转换为可执行的Java类。类加载器的作用不仅仅是加载类,还包括确保类的唯一性,避免重复加载,并且遵循特定...

    java的ClassLoader类加载器机制

    Java的ClassLoader类加载器机制 在 Java 虚拟机(JVM)中,类加载器(ClassLoader)扮演着非常重要的角色。类加载器负责加载 Java 类,包括核心类和用户自定义类。在 JVM 运行过程中,类加载器会形成一个层次结构,...

    ClassLoader类加载器

    2. Extension ClassLoader:扩展类加载器,负责加载`&lt;JAVA_HOME&gt;/lib/ext`目录下的扩展类库,或者被`-Djava.ext.dirs`指定的路径中的类。 3. Application ClassLoader:也称为系统类加载器,负责加载用户类路径`-cp...

    Java类加载器.pdf

    2. **Extension ClassLoader(扩展类加载器)**:它加载位于`JAVA_HOME/lib/ext`目录下的JAR包和用户指定的扩展目录下的类库。 3. **System ClassLoader(系统类加载器)**:也称为应用程序类加载器,它负责加载用户...

    java 类加载器 加密

    Java中的类加载器主要有三种:Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。它们按照双亲委派模型工作,从根加载器开始,逐级尝试加载类。当我们想要对类进行加密时,可以创建一个自定义的类...

    java类加载器实例

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

    类加载器文件

    **类加载器(ClassLoader)**是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译好的`.class`文件加载到JVM中,使得这些类可以在Java环境中运行。类加载器不仅能够加载类,还能够根据不同的需求定制加载方式,如从...

    java类加载器-tomcat中的类加载器

    在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt.jar;Extension ClassLoader则加载JRE扩展目录下的jar文件...

    Java的类加载器

    Java的类加载器主要有Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader是JVM启动时的第一个类加载器,负责加载JDK的`&lt;JAVA_HOME&gt;/jre/lib`目录下的核心类库。Extension ...

    深入探讨 Java 类加载器

    【深入探讨 Java 类加载器】 Java 类加载器是Java虚拟机(JVM)的核心组成部分,它的主要任务是将Java类的字节码加载到JVM中以便执行。类加载器的概念始于JDK 1.0,最初是为了解决Java Applet在浏览器中的运行需求...

Global site tag (gtag.js) - Google Analytics