文档章节

ClassLoader实现分析

AbeJeffrey
 AbeJeffrey
发布于 2017/04/03 18:18
字数 2500
阅读 51
收藏 5

Java源代码被编译成class字节码,最终需要加载到虚拟机中才能运行。整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。

什么是类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。要加载一个类,虚拟机需完成以下工作:

1、通过一个类的全限定名获取描述此类的二进制字节流;
2、将这个字节流所代表的静态存储结构保存为方法区的运行时数据结构;
3、在java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区的入口;

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器缓存Java类型的二进制表现形式,在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(通过抛出LinkageError异常的子类)。也即说,如果这个类一直没有被程序主动使用,那么类加载器就不会报告该错误。

ClassLoader

ClassLoader用来加载 class字节码到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源文件在经过 Javac之后就被转换成 Java 字节码文件(.class 文件)。ClassLoader负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

ClassLoader虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟中的唯一性。也就是说,比较两个类是否“相等”,只有在两个类是由同一个类加载器的前提之下才有意义,否则,即使这两个类来源于同一个class文件,只要加载它的类加载器不同,那这两个类必定不相等。这里所指的“相等”包括代表类的Class对象的equal方法、isAssignableFrom()、isInstance()方法及instance关键字返回的结果。

类加载器分类

JVM提供了3种类加载器:
1、启动类加载器(Bootstrap ClassLoader):底层由C++编写,随JVM启动而启动,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
2、扩展类加载器(Extension ClassLoader):由sun.misc.Launcher$ExtClassLoader实现,负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
3、应用程序类加载器(Application ClassLoader):由sun.misc.Launcher$AppClassLoader实现,负责加载用户路径(classpath)上的类库。

除了这三类默认的ClassLoader,用户可以根据需求继承java.lang.ClassLoader定义自己的ClassLoader。

双亲委派模型

双亲委派模型工作过程:当一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,只有当父类加载器无法完成加载任务时(抛出ClassNotFoundException),才会尝试执行加载任务。

双亲委派模型的好处:

Java类随着它的ClassLoader一起具备了一种带优先级的层次关系。例如java.lang.Object存放在rt.jar之中,无论那个类加载器要加载这个类,最终都是委托给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类;同时双亲委派模型也保证了类型被使用的安全性。

下面将结合源码(基于java8)分析ClassLoader原理。

ClassLoader主要成员域

//用于委派的父加载器
private final ClassLoader parent;

//当前ClassLoader使用并行模式加载类时,用于保存类名及其锁对象
//jvm也使用该对象来判断当前ClassLoader是否具备并行能力和拥有正确的锁来加载类
private final ConcurrentHashMap<String, Object> parallelLockMap;

//用于保存包和对应的签名
private final Map <String, Certificate[]> package2certs;

//在具有无符号类的所有包之间共享
private static final Certificate[] nocerts = new Certificate[0];

//缓存当前ClassLoader加载的类,避免被GC回收,只有ClassLoader被GC回收,其加载的class才会被回收
private final Vector<Class<?>> classes = new Vector<>();

//默认域
private final ProtectionDomain defaultDomain =
    new ProtectionDomain(new CodeSource(null, (Certificate[]) null),
                             null, this, null);
//该ClassLoader加载的所有类的ProtectionDomain
private final Set<ProtectionDomain> domains;

//用于保存当前ClassLoader中定义的package
private final HashMap<String, Package> packages = new HashMap<>();

ClassLoader构造方法

   
   private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {//使用并行模式加载class
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;//没有更细粒度的锁,因此直接锁定ClassLoader
        }
    }
    //使用指定的父加载器来创建ClassLoader对象
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    //使用getSystemClassLoader()返回值作为父加载器来创建ClassLoader对象
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

下面介绍ClassLoader的几个关键方法。

loadClass

    //加载指定名称的class
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {//首先获取锁
            //检查class是否已经加载,已加载则返回对应的Class对象
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {//实现双亲委派加载机制
                    if (parent != null) {
                        c = parent.loadClass(name, false);//委托父类加载
                    } else {
                        c = findBootstrapClassOrNull(name);//委托Bootstrap ClassLoader加载
                    }
                } catch (ClassNotFoundException e) {
                    // 父类加载器未找到该类
                }

                if (c == null) {
                    //未找到则调用自己的findClass加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {//如果类需要resolve,在调用resolveClass进行链接
                resolveClass(c);
            }
            return c;
        }
    }
    /** 如果当前ClassLoader具备并行加载能力,则使用细粒度的锁,每个class对象一个锁;
      * 否则,直接返回该classLoader上的锁
      */
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }
    //负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native方法。
    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
    private native final Class<?> findLoadedClass0(String name);

从源码可知,loadClass方法实现了双亲委派的类加载机制,如果需要自定义类加载器,应该重写内部的findClass方法,而非loadClass方法。

findClass

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

该方法默认抛出ClassNotFoundException,表明其行为应该由实现委托模型后面的ClassLoader来实现。

findSystemClass

    protected final Class<?> findSystemClass(String name)
        throws ClassNotFoundException
    {
        ClassLoader system = getSystemClassLoader();//获取系统类加载器
        if (system == null) {
            if (!checkName(name))
                throw new ClassNotFoundException(name);
            Class<?> cls = findBootstrapClass(name);//使用启动类加载器加载类
            if (cls == null) {
                throw new ClassNotFoundException(name);
            }
            return cls;
        }
        return system.loadClass(name);
    }

此方法主要通过sun.misc.Launcher$AppClassLoader来加载类,当没有系统类加载器时,在尝试委托Bootstrap ClassLoader加载加载。

defineClass

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);//调用本地方法
        postDefineClass(c, protectionDomain);
        return c;
    }
    /** 确定ProtectionDomain,并做如下检查:
      * 1 没有定义java.开头的类(类的全路径名)
      * 2 当前类的签名者与包中的其余类的签名者匹配
      */
    private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }
    //返回类对应的源码路径
    private String defineClassSourceLocation(ProtectionDomain pd)
    {
        CodeSource cs = pd.getCodeSource();
        String source = null;
        if (cs != null && cs.getLocation() != null) {
            source = cs.getLocation().toString();
        }
        return source;
    }
    //设置签名者
    private void postDefineClass(Class<?> c, ProtectionDomain pd)
    {
        if (pd.getCodeSource() != null) {
            Certificate certs[] = pd.getCodeSource().getCertificates();
            if (certs != null)
                setSigners(c, certs);
        }
    }

defineClass 此方法负责将二进制字节流转换为Class对象。

getSystemClassLoader

上面代码中有几个地方用到getSystemClassLoader(),下面来分析它的实现:

    //保存系统类加载器对象
    private static ClassLoader scl;
    //是否已经设置系统类加载器
    private static boolean sclSet;
    /**  用于返回系统类加载器,new ClassLoader实例时,用于默认的委派parent,通常用于启动应用的类加载器
      *  该方法在运行时的启动顺序中早期被调用,旨在创建系统类加载器并被设置为Thread的上下文加载器
      *  @CallerSensitive 用于防止双重反射
      */
    @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();//初始化
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {//加锁保证只被初始化一次
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
           /**  返回Launcher实例,Launcher实例被设计为单例,初始化Launcher实例时,主要做以下几件事:
             *  1 创建ExtClassLoader的实例
             *  2 根据ExtClassLoader的实例创建AppClassLoader的实例
             *  3 将AppClassLoader的实例设置为当前线程的上下文加载器
             *  4 创建SecurityManager的实例
             */
            //有兴趣可参见Launcher源码查看具体实现
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

在ClassLoader实现中,不只加载类遵循双亲派模型,获取类相关的资源也同样遵循双亲派模型,由源码即可知:

    public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

这很好理解,类相关的resource的url当然由加载该class的ClassLoader去获取更安全可靠。

欢迎指出本文有误的地方,转载请注明原文出处https://my.oschina.net/7001/blog/872840

© 著作权归作者所有

共有 人打赏支持
AbeJeffrey
粉丝 31
博文 43
码字总数 116062
作品 0
杭州
高级程序员
知识总结 插件化学习 Activity加载分析

现在安卓插件化已经很成熟,可以直接用别人开源的框架实现自己项目,但是学习插件化的实现原理是安卓研发工程师加深安卓系统理解的很好途径。 安卓插件化学习 插件Activity加载方式分析 实现...

CankingApp
2017/05/19
0
0
知识总结之 插件化基础ClassLoader

安卓插件化技术已经作为一个优秀的合格研发必备要求,学习和掌握现有不同种类动态加载方案 是提升个人技术深度有效途径。 插件化基础 ClassLoader ClassLoader是什么? ClassLoader 是将jav...

CankingApp
2017/05/05
0
0
JVM类加载器-源码分析

前言 我们在JVM类加载器-原理一文中了解了JVM类加载器的基本原理。现在我们一起通过ClassLoader类及其相关源码来详细分析、理解JVM类加载器的体系,深入理解JVM类加载器的原理与实现。 Clas...

Justlearn
2017/04/30
0
0
Do You Really Know ClassLoader?

主要是对ClassLoader相关的知识进行一个总结,讨论和解决以下问题: Class和ClassLoader是什么关系,Class.forName 和ClassLoader.loadClass的联系与区别? new的过程是什么,在jvm中如何执行...

zqrferrari
2017/08/10
0
0
java classloader的一个bug

最近遇到一个很诡异的问题,SAE的javaruntime有一部分用户反馈session总是丢失,导致相当一部分用户的代码部署后登录功能无法正常使用。 先说下sae session的托管方式: 这里先说下SAE的ses...

nero_zy
2016/01/08
104
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MySQL 乱七八糟的可重复读隔离级别实现

MySQL 乱七八糟的可重复读隔离级别实现 摘要: 原文可阅读 http://www.iocoder.cn/Fight/MySQL-messy-implementation-of-repeatable-read-isolation-levels 「shimohq」欢迎转载,保留摘要,谢...

DemonsI
今天
2
0
Spring源码阅读——2

在阅读源码之前,先了解下Spring的整体架构: 1、Spring的整体架构 1. Ioc(控制反转) Spring核心模块实现了Ioc的功能,它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描...

叶枫啦啦
今天
1
0
jQuery.post() 函数格式详解

jquery的Post方法$.post() $.post是jquery自带的一个方法,使用前需要引入jquery.js 语法:$.post(url,data,callback,type); url(必须):发送请求的地址,String类型 data(可选):发送给后台的...

森火
今天
0
0
referer是什么意思?

看看下面这个回答(打不开网页可以把网址复制到搜索栏): https://zhidao.baidu.com/question/577842068.html

杉下
今天
1
0
使用U盘安装CentOS-解决U盘找不到源

1. 使用UltraISO制作CentOS安装盘 如果需要安装带界面的系统,为保证安装顺利,可选择Everything版本的ISO制作安装盘。 2. 在BIOS中选择使用U盘安装 系统启动后,进入安装选择界面,其中有三...

Houor
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部