【正文】Java类加载器(  CLassLoader ) 死磕3: 

揭秘 ClassLoader抽象基类

本小节目录

3.1. 类的加载分类:隐式加载和显示加载

3.2. 加载一个类的五步工作

3.3. 如何获取类的加载器

3.4 解刨加载器——ClassLoader抽象基类揭秘

3.5. loadClass 关键源代码分析

3.1. 揭秘ClassLoader抽象基类

3.1.1. 类的加载分类:隐式加载和显示加载

java中类是动态加载的,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。一是加快启动的速度,二是节约内存。如果一次性加载全部jar包的所有class,速度会很慢。

动态载入一个class类,有两种方式:

(1) implicit隐式加载

即通过实例化才载入的特性来动态载入class。比如:

IPet dog=new Dog();

在新建对象时,如果Dog.class类没有加载,则JVM 会在背后调用当前的AppClassLoader,加载Dog.class,并且初始化。

(2) explicit显式加载

explicit显式方式,又分两种方式:

一是:java.lang.Class的forName()方法。 比如:

Class<?> aClass = Class.forName(className);

二是:java.lang.ClassLoader的loadClass()方法。 比如:

FileClassLoader classLoader = new FileClassLoader(null,baseDir);

Class<?> aClass = classLoader.loadClass(className);

下面有两个问题:

两种显示加载的方式,有何区别呢?

两种显示加载的方式,有何联系呢?

花开两朵,各表一枝。 现在先介绍通过第二种ClassLoader显式加载方法 ,其类的加载过程。然后再进行两种方式的比对。

在介绍ClassLoader显式加载前,先回顾一下类的加载过程。

3.1.2. 加载一个类的五步工作

在疯狂创客圈的《死磕java》工程源码中,有一个常用的系统属性的配置类——SystemConfig 。下面以次为例,展示一下类的加载过程。

源代码如下:

package com.crazymakercircle.config;

.....................

@ConfigFileAnno(file = "/system.properties")

public class SystemConfig extends ConfigProperties

{

    static

    {

        Logger.info("开始加载配置文件到SystemConfig");

        //依照注解装载配置项

        loadAnnotations(SystemConfig.class);

    }

   ....................................

    @ConfigFieldAnno(proterty = "debug")

    public static boolean debug;

    /**

     * 宠物工厂类的名称

     */

    @ConfigFieldAnno(proterty = "pet.factory.class")

    public static String PET_FACTORY_CLASS;

    /**

     * 宠物模块的类路径

     */

    @ConfigFieldAnno(proterty = "pet.lib.path")

    public static String PET_LIB_PATH;

}

这个类的主要功能是,从配置文件中加载配置项,方便编程时使用。

编译完成后,这个”.java”文件经过Java编译器编译成拓展名为”.class”文件——SystemConfig.class,这个”.class”文件中保存着Java代码经转换后的虚拟机指令。

当需要使用这个类时,虚拟机将会加载它的SystemConfig.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这个就是类加载的过程。

类加载的过程分为五步:加载、验证、准备、解析、初始化。


一、加载:

通过一个类的完全限定名称,查找此类字节码”.class”文件,读入内存形成字节码流。并创建一个Class对象。

二、验证

检查字节流中包含信息符合虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

三、准备

主要的工作是,在方法区分配静态变量的内存,并且进行内存的初始化,设置初始值即0。

四、解析

主要将常量池中的符号引用进行翻译,翻译为直接引用,也就是在内存中的地址。

五、初始化

首先,如果类的存在静态变量需要进行赋值,在这个阶段完成。

其次,如果有static 静态块,在这个阶段执行。

再次,若该类具有超类,则对其进行初始化。

实例中,通过这一步,完成执行SystemConfig 类的static块的执行,并且为每一个配置项赋值,因为都是静态的。

通过这个五步,一个类的全限定名的”.class”文件,完成了转换为一个与目标类对应的java.lang.Class对象实例的工作。 实际的工作,远远比上面的陈述的负责。为了方便理解和记忆,上面进行了大大的简化,只是提取出主要的特征。

以上五步的中间3个步骤,验证、准备、解析,合起来有一个统称,叫类的链接。

3.1.3. 如何获取类的加载器

使用 getClassLoader() 方法,可以取得类的加载器。如果是应用程序的class path下的类,加载器一般为AppClassLoader 类型。当前,并不是绝对的,这个后面讲到如何去定制和修改。

查看一下当前实例的应用程序类所属的classLoader,代码如下:

 public static void showCurrentClassLoader()

    {

        Logger.info("");

        Logger.info("显示当前类的ClassLoader::");

        Logger.info("class=" + ClassLoaderDemo.class.getCanonicalName());

        ClassLoader loader = ClassLoaderDemo.class.getClassLoader();

        Logger.info("Loader=" + loader.toString());

    }

结果如下:

showCurrentClassLoader |>  显示当前类的ClassLoader::

showCurrentClassLoader |>  class=com.crazymakercircle.classLoaderDemo.base.ClassLoaderDemo

showCurrentClassLoader |>  Loader=sun.misc.Launcher$AppClassLoader@18b4aac2

在显示加载场景中,A加载B,一般情况下,B的加载器就是A的加载器。演示类ClassLoaderDemo加载了SystemConfig类,看看后者的加载器是啥?

代码如下:

public static void showAppLoader()

    {

        String className = "com.crazymakercircle.config.SystemConfig";

        Class<?> target = null;

        try

        {

            //根据类名 显示加载加载类

            target = Class.forName(className);

        } catch (ClassNotFoundException e)

        {

            e.printStackTrace();

        }

        Logger.info("显示加载的类的ClassLoader:");

        Logger.info("class=" + target.getCanonicalName());

        ClassLoader loader = target.getClassLoader();

        Logger.info("Loader=" + loader.toString());

    }

结果如下:

 showAppLoader |>  显示加载的类的ClassLoader:

       showAppLoader |>  class=com.crazymakercircle.config.SystemConfig

       showAppLoader |>  Loader=sun.misc.Launcher$AppClassLoader@18b4aac2

%java.ext.dirs% 下jar中类的加载器,类型一定为Extention ClassLoader。比方说,DNSNameService 域名服务类是一个典型的 lib\ext 中的dnsns.jar包中的类,我们来看下他的加载器。代码如下:

public static void showExtClassLoader()

    {

        ClassLoader loader = DNSNameService.class.getClassLoader();

        Logger.info("");

        Logger.info("%java.ext.dirs% 下类的ClassLoader:");

        Logger.info("class=" + DNSNameService.class.getCanonicalName());

        Logger.info("Loader=" + loader.toString());

    }

结果如下:

showExtClassLoader |>  %java.ext.dirs% 下类的ClassLoader:

  showExtClassLoader |>  class=sun.net.spi.nameservice.dns.DNSNameService

  showExtClassLoader |>  Loader=sun.misc.Launcher$ExtClassLoader@12a3a380

%java.home% 下jar中类的加载器,我们可以理所当然的认为,一定是Bootstrap ClassLoader 加载器类型。 比方说,String 类是一个典型的系统属性%java.home% 目录下 rt.jar 中的类,我们来看下他的加载器。代码如下:

    public static void showBootstrapClassLoader()

    {

        ClassLoader loader = String.class.getClassLoader();

        Logger.info("");

        Logger.info("%java.home%下类的ClassLoader:");

        Logger.info("class=" + String.class.getCanonicalName());

        Logger.info("Loader=" + loader);

    }

遗憾的时,结果与前面两个加载器,大不一样。

结果如下:

showBootstrapClassLoader |>  %java.home%下类的ClassLoader:

showBootstrapClassLoader |>  class=java.lang.String

showBootstrapClassLoader |>  Loader=null

1.1.4. 解刨加载器——揭秘ClassLoader抽象基类

ClassLoader类是Java 中的 所有ClassLoader 的基类,在java.lang包中。这是一个抽象类。

ClassLoader类,包含了所有类加载器的三个重要组成部分:

(1)加载成功的Class 对象的缓存;

(2)类的查找路径

(3)loadClass(String name)

ClassLoader加载器将加载成功的Class 对象,加入一个Vector 动态数组中,避免重复加载。 这个Vector 动态数组,也就是加载成功的classes的缓存。

源码如下:

// The classes loaded by this class loader. The only purpose of this table

// is to keep the classes from being GC'ed until the loader is GC'ed.

private final Vector<Class<?>> classes = new Vector<>();

前面提到,每一个加载器,都有一个自己的查找范围,是一系列的jar包或者类路径,称之为查找路径。形象的说,这个查找路径,就像是ClassLoader加载类的专属的地盘。

其源码如下:

 // The paths searched for libraries

 private static String usr_paths[];

 private static String sys_paths[];

讲了这么多,终于到了关键点——ClassLoader加载一个类的秘密在哪里?。

加载类的方法是:

ClassLoader的loadClass(String name)方法,是类加载器中一个比较重要的方法。其作用是,用于加载一个类。

这个方法,比较理想的流程,大致如下面所示:

(1)首先在缓存中找,是否已经加载。如果找到就返回。

(2)如果找不到,就去查找路径查找文件。如果找出类的.class字节码,则去完成加载一个类的五步工作,放入自己的缓存中,然后返回给调用者。

loadClass(String name)方法,充分将前面的 classes 缓存和查找路径利用起来,将他们串在了一起。

实际上,这仅仅只是一个理想化的结果。

实际的类加载流程,远远比以上的假想流程,复杂得多。

直接来看 loadClass 方法的源代码吧,这样反而简单、粗暴、直接。

1.1.5. loadClass 关键源代码分析

loadClass 方法的源代码如下:

protected Class<?> loadClass(String name, boolean resolve)

        throws ClassNotFoundException

    {

        synchronized (getClassLoadingLock(name)) {

    //在加载器的缓存中,按照类名,查找已经加载好的现成类

            Class<?> c = findLoadedClass(name);

    //如果加载成功,就直接返回了

    //如果还没有找到,尴尬了

    if (c == null) {

                ....

 try {

                    if (parent != null) {

                      //优先让父加载器去加载,若父加载器不为空的话

                        c = parent.loadClass(name, false);

                    } else {

                    //若父加载器为空,则通过Bootstrap Classloader 加载

                        c = findBootstrapClassOrNull(name);

                    }

                } catch (ClassNotFoundException e) {

                   ....

                }

                if (c == null) {

                    // 如果父加载器、启动类加载器,都没有找到

                    // 才调用findclass,从自己的地盘,类路径加载

                    c = findClass(name);

                   ....

                }

            }

             .......

            return c;

        }

    }

}

上面的代码中,与流程无关的代码直接省略了。

概况一下,loadClass的大致流程如下:

1. 执行findLoadedClass(String)去缓存检测这个class是不是已经加载过了。 在加载器的缓存中,按照类名,查找已经加载好的现成类。如果找到,直接返回了。

2. 如果没有找到,执行parent父加载器的loadClass方法。通过父加载器加载。注意:这里不是去自己的地盘查找class文件,而是优先通过父加载器加载。这点,非常重要。具体原因,后面会讲到。

3. 若父加载器为空,则通过Bootstrap Classloader 加载。

4. 如果父加载器、启动类加载器,都没有找到,才调用findclass,从自己的地盘,自己的类路径去查找字节码文件,通过findClass(String)查找。

抽丝剥茧之后,loadClass 方法的源代码其实也不过如此,比较简单。

但是,这里已经有两个疑问:

(1)一个加载器的parent是谁?

(2)为什么优先从parent加载,而不是从自己的地盘加载?

欲知后事如何,请看下回分解。

源码:

代码工程:  classLoaderDemo.zip

下载地址:在疯狂创客圈QQ群文件共享。

疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
QQ群链接:疯狂创客圈QQ群

无编程不创客,无案例不学习。 一定记得去跑一跑案例哦

类加载器   全目录

1.导入

2. JAVA类加载器分类

3. 揭秘ClassLoader抽象基类

4. 神秘的双亲委托机制

5. 入门案例:自定义一个文件系统的自定义classLoader

6. 基础案例:自定义一个网络类加载器

7. 中级案例:设计一个加密的自定义网络加载器

8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理

9. 高级案例2:上下文加载器原理和案例

Java类加载器(死磕3)的更多相关文章

  1. Java类加载器(死磕 1-2)

      Java类加载器(  CLassLoader ) 死磕 1.2:  导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...

  2. Java类加载器(死磕5)

    Java类加载器(  CLassLoader )  死磕5:  自定义一个文件系统classLoader 本小节目录 5.1. 自定义类加载器的基本流程 5.2. 入门案例:自定义文件系统类加载器 5 ...

  3. Java类加载器( 死磕9)

    [正文]Java类加载器(  CLassLoader ) 死磕9:  上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...

  4. Java类加载器( 死磕7)

    [正文]Java类加载器(  CLassLoader )死磕7:  基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...

  5. Java类加载器( 死磕8)

    [正文]Java类加载器(  CLassLoader ) 死磕 8:  使用ASM,和类加载器实现AOP 本小节目录 8.1. ASM字节码操作框架简介 8.2. ASM和访问者模式 8.3. 用于增 ...

  6. Java类加载器( 死磕 6)

    [正文]Java类加载器(  CLassLoader )死磕 6:  自定义网络类加载器 本小节目录 6.1. 自定义网络类加载器的类设计 6.2. 文件传输Server端的源码 6.3. 文件传输C ...

  7. Java类加载器( 死磕 4)

    [正文]Java类加载器(  CLassLoader ) 死磕 之4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...

  8. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  9. java类加载器深入研究

    看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...

随机推荐

  1. 关于PHP接收HTTP模拟POST传JSON格式时$_POST为空的问题

    编写项目时需要将数据转换成json格式的字符串,并通过post传参传给后台,但在后台接收数据时发现$_POST参数为空 头部为: curl_setopt($ci, CURLOPT_HEADER, 0) ...

  2. Mac item2 配色,大小写敏感及常用快捷键

    http://blog.csdn.net/lainegates/article/details/38313559 目录(?)[-] 配色 大小写敏感 快捷揵   item2是mac下非常好用的一款终端 ...

  3. EventBus3.0使用笔记.md

    事件总线这个其实没什么好说的,除了已经ondestroy的fragment或者activity不能接受外,只要定义了的都能接收消息 代码如下,需要注意的一点就是接收的监听事件必须用public修饰并且 ...

  4. Android图片缓存之Glide进阶(四)

    前言: 前面学习了Glide的简单使用(http://www.cnblogs.com/whoislcj/p/5558168.html),今天来学习一下Glide稍微复杂一点的使用. GlideModu ...

  5. Android图片缓存之Bitmap详解(一)

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. Bitmap: Bitmap是Android ...

  6. httplib 和 httplib2区别之 gzip解压

    HTTP请求头Accept-encoding: gzip信息告诉服务器,如果它有任何新数据要发送给时,请以压缩的格式发送.如果服务器支持压缩,它将返回由 gzip 压缩的数据并且使用Content-e ...

  7. C# Http方式下载文件到本地

    下文代码是从网络(http://www.cnblogs.com/hayden/archive/2012/04/26/2472815.html)得来,亲测好用.我中修改了下格式和注释,版权属于原作者“舒 ...

  8. django 设置局域网内访问项目

    1. 关闭主机电脑上的防火墙(或者不用关闭,加一个端口号就行) 2.在你的settings.py文件中,找到ALLOWED_HOSTS=[ ],在中括号中加入你在局域网中的IP.例:我在局域网中的IP ...

  9. 5.4 heapq--堆队列算法

    本模块实现了堆队列算法,也叫作优先级队列算法.堆队列是一棵二叉树.而且拥有这样特点,它的父节点的值小于等于不论什么它的子节点的值,假设採用数组array实现,能够把它们的关系表示为:heap[k] & ...

  10. xml实现AOP

    1.使用<aop:config></aop:config> 2.首先我们需要配置<aop:aspect></aop:aspect>,就是配置切面 2.1 ...