文档章节

ClassLoader原理分析

haoran_10
 haoran_10
发布于 2016/07/15 16:37
字数 1443
阅读 11
收藏 0

前文:Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的。

类装载器所做的工作实质是把类文件从硬盘读取到jvm运行内存中,或者从网络中读取到jvm运行内存中

JVM在加载类的时候,都是通过ClassLoaderloadClass()方法来加载class的。

 

例如:

public class TestClassLoader {

    public static void main(String[] args) {
        System.out.println(TestClassLoader.class.getClassLoader());
    }
}

 运行结果:

sun.misc.Launcher$AppClassLoader@4e0e2f2a

 

但是ClassLoader是怎样加载class的呢?

 

一、java ClassLoader体系

Java默认是有三个ClassLoader,按层次关系从上到下依次是:

  • Bootstrap ClassLoader
  • ExtClassLoader
  • AppClassLoader

 

Bootstrap ClassLoader是最顶层的ClassLoader,比较特殊,是用C++编写集成在JVM中的,是JVM启动的时候用来加载一些核心类的,

比如:rt.jar,resources.jar,charsets.jar,jce.jar 

 

import java.net.URL;

public class TestClassLoader {

    public static void main(String[] args) {
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {  
            System.out.println(urls[i].toExternalForm());  
        }
    }
}

 

 

运行结果为:

 

file:/C:/Program%20Files/Java/jre1.8.0_73/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/classes

 

 

Bootstrap ClassLoader加载的类全都是java自有的核心类。

ExtClassLoader、AppClassLoader都是继承自Bootstrap ClassLoader。

 

ExtClassLoader是用来加载扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有的jar

 

AppClassLoader叫做系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件,包括我们平时运行jar包指定cp参数下的jar包

 

 

我们问题是,虽然有三个类加载器,但是jvm怎样知道一个java 类是哪个ClassLoader来加载的呢,使用什么规则或者算法?

 

二、ClassLoader双亲委派模式

通过查看java.lang.ClassLoader(所有的类加载器都是继承该类)的

public Class<?> loadClass(String name) throws ClassNotFoundException ;

又调用了protected Class<?> loadClass(String name, boolean resolve)

        throws ClassNotFoundException;

 

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果父加载器不为空,使用父加载器加载class
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果父加载器也为空,使用bootstarpClassLoader加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //如果还为空,调用自定义的加载方法
                    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;
        }
    }

 

 

 

 

 

这时问题是,parent是什么?

public abstract class ClassLoader {

    // The parent class loader for delegation
    private final ClassLoader parent;
    
    ...
}

 

原来ClassLoader内部使用了父加载器,这就是双亲委托模式。

 

如图所示:

 

 

 当前类加载器先不加载class,委托给父加载器加载class,如果父加载器没有加载成功,本类加载器再加载class。

 

可以使用下面代码测试下:

public class TestClassLoader {

    public static void main(String[] args) {
        ClassLoader loader = MyAppClassLoader.class.getClassLoader();
        while(loader != null) {
            System.out.println(loader);
            loader = loader.getParent();   
        }
        System.out.println(loader);
    }
}

 

运行结果为:

sun.misc.Launcher$AppClassLoader@4e0e2f2a
sun.misc.Launcher$ExtClassLoader@2a139a55
null

 

这样做的好处是:

为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String,同时也避免了重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException

 

三、自定义ClassLoader

通常我们不需要自己实现ClassLoader,但是在一些特殊的场景需要自定义ClassLoader,比如tomcat加载webApp下面的Class,或者一些RPC框架,或者从文件流中,等等,我们需要继承java.lang.ClassLoader ,

比如:

public class MyAppClassLoader extends ClassLoader{
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        //从特殊路径下读取class,或者从网络中读取class,不管咋样,实现自己的方法,返回加载class
        
        //...
    }
}

 

四、ContextLoader当前classLoader

首先关于类的加载补充一点就是如果类A是被一个加载器加载的,那么类A中引用的B也是由这个加载器加载的(如果B还没有被加载的话),通常情况下就是类B必须在类A的classpath下。

那么问题来了。某些时候这种顺序机制会造成困扰,特别是jvm需要动态载入有开发者提供的资源时。Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JNDI,JDBC,等等。

这里以JNDI为例,JNDI的类是由bootstarp ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?

 

这时JAVA引入了线程上下文类加载的概 念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。

Thread两个方法:

public ClassLoader getContextClassLoader() ;

public void setContextClassLoader(ClassLoader cl) ;

 

我们在加载动态类的时候,可以是在当前上下文环境下的特定的ClassLoader,然后通过当前上下文的ClassLoader去加载动态类,这样就可以摆脱双亲委托模式了。

如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。

 

 

© 著作权归作者所有

共有 人打赏支持
haoran_10
粉丝 25
博文 88
码字总数 80846
作品 0
杭州
程序员
知识总结 插件化学习 Activity加载分析

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

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

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

Justlearn
2017/04/30
0
0
深入Spring Boot:ClassLoader的继承关系和影响

前言 对spring boot本身启动原理的分析,请参考:http://hengyunabc.github.io/spring-boot-application-start-analysis/ Spring boot里的ClassLoader继承关系 可以运行下面提供的demo,分别...

横云断岭
2017/08/19
0
0
Dalvik虚拟机中Java类的加载过程

  首先需要明白一点,Java类在使用之前必须先被加载。关于加载其实分为显式加载和隐式加载,显式加载就是我们通过自己来调用相应的加载方法来对特定的类进行加载,隐式加载就是在使用类的时...

酷客创意网
2017/12/27
0
0
深入分析ClassLoader原理

一、什么是ClassLoader? 一个Java程序不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能...

刘诗书
2017/11/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

wait()被notify()后,接着执行wait()后面的语句

wait()被notify()后,接着执行wait()后面的语句

noteman
15分钟前
0
0
Ubuntu集群-使用MAAS开始裸机安装

Ubuntu使用MAAS装机的七个步骤。 1、Setup your hardware You need one small server for MAAS and at least one server which can be managed with a BMC. It is recommended to have the M......

openthings
28分钟前
2
0
OSX | SafariBookmarksSyncAgent意外退出解决方法

1. 启动系统, 按住⌘-R不松手2. 在实用工具(Utilities)下打开终端,输入csrutil disable, 然后回车; 你就看到提示系统完整性保护(SIP: System Integrity Protection)已禁用3. 输入reboot回车...

云迹
今天
4
0
面向对象类之间的关系

面向对象类之间的关系:is-a、has-a、use-a is-a关系也叫继承或泛化,比如大雁和鸟类之间的关系就是继承。 has-a关系称为关联关系,例如企鹅在气候寒冷的地方生活,“企鹅”和“气候”就是关...

gackey
今天
4
0
读书(附电子书)|小狗钱钱之白色的拉布拉多

关注公众号,在公众号中回复“小狗钱钱”可免费获得电子书。 一、背景 之前写了一篇文章 《小狗钱钱》 理财小白应该读的一本书,那时候我才看那本书,现在看了一大半了,发现这本书确实不错,...

tiankonguse
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部