JAVA 类加载

Java类的加载是由虚拟机来完成的,虚拟机把描述类的class文件加载到到内存,并对数据进行校验、解析、初始化,最终形成能被Java虚拟机直接使用的Java类型。

概述

那么类加载器是什么呢
类加载器是一个用来加载类文件的类,Java源代码通过javac编译器编译成类文件,然后jvm来执行类文件的字节码。类加载器负责加载文件系统、网络或者其他来源的类文件。

默认类加载器的分类

  • Bootstrap加载器:加载rt.jar中的jdk类文件,它是所有类加载器的父加载器。又称初始加载器
  • Extension类加载器:先委托给父加载器,也就是Bootstrap,如果没有成功的话,再从jre/lib/ext目录下获取java.ext.dirs系统属性定义的目录下加载类。
  • System类加载器:又称Application类加载器,他负责从classpath环境变量中加载默写应用相关的类,它是Extension类加载器的子加载器,
  • 除了Bootstrap类加载器是大部分有C写的,其他的类加载器都是通过java.lang.ClassLoader来实现的。

这里说明一下java.lang.ClassLoader中几个重要的方法

//加载指定名称(包括包名)的二进制类型,供用户调用的接口
public Class<?> loadClass(String name);
//加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是,这里的resolve参数不一定真正能达到解析的效果),供继承用
protected synchronized Class<?> loadClass(String name, boolean resolve);
protected Class<?> findClass(String name)
//定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}

加载过程

类加载过程:分为三步:装载、链接、初始化,其中链接又分为三个步骤:验证、准备、解析

  1. 装载:
    1. 通过一个类的权限定名来获取定义此类的二进制字节流
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    3. 在java堆中生成一个代表这个类的java.lang.class对象,做为方法去这些数据的访问入口
  2. 链接:
    1. 验证:确保被加载类的正确性,如果有一个恶意的class文件,让jvm执行,那就不好了,所以要先验证是否安全,才会通过。
    2. 准备:为类的静态变量分配内存空间,并且将其初始化为默认值,比如private static Int i = 0;,这些内存将在方法区中进行分配,需要说明的是,这时候进行内存分配的仅是static修饰的变量,而不是实例变量,实例变量将会在对象实例化时随着对象一起分配在java的堆中。
    3. 解析:把类中符号引用转换为直接引用
  3. 初始化:为类的静态炳龙赋予正确的初始值

那么java类的生命周期就产生了

上述中类初始化说到,那么问题:

  1. 类在什么情况会初始化
  2. 类初始化

首页第一个问题:类在什么时候初始化

  • 创建实例的时候,例如new 一个对象。
  • 访问类的静态变量或者静态方法
  • 类反射
  • 初始化类的子类
  • JVM启动标明的启动类,就是文件名和类名相同的那个类

第二个问题:类初始化

  • 如果这个类 还没有被装载和链接,那就先装载和链接
  • 如果这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次)那就初始化直接父类(注意:这个不适用于接口)
  • 加入类中存在初始化语句(如Static变量和static块),那就依次执行这些初始化语句。

其中要说到双亲委派加载机制

当一个类收到类加载请求时,请把请求委派给父类去完成,每个层次的类加载都是如此,所以 所有的加载请求都传到了启动类中去加载,只有当父类加载器返回自己无法加载的时候,子类加载器才会尝试自己加载。

如图

优点:类加载器具备了优先级层次关系,所有加载请求都汇总到顶层的启动类加载器,例如 java.lang.Object (存放在jre\lib\rt.jar中)它是所有Java类的父类,因此Object会由类加载器来加载,并且加载的都是同一个类。如果不用双亲委派加载模式,每个类自行加载,会出现多个object,程序没法控制。

看源码(jdk 1.8)

//方法传的参数:类名包名,链接一个指定的类。这是一个在某些情况下确保类可用的必要方法,详见 Java 语言规范中“执行”一章对该方法的描述。
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 {
                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
            }
            //如果父类加载器或者系统加载器都加载失败,则使用自己的findClass方法加载
            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;
    }
}

这是在网上的一个例子 双亲委派模型代码实例验证

public class ClassLoaderTest {

    public static void main(String args[]){
        showMsg("ClassLoaderTest 类的加载器的名称:"+ ClassLoaderTest.class.getClassLoader().getClass().getName());
        showMsg("System 类的加载器的名称:"+ System.class.getClassLoader());
        showMsg("List 类的加载器的名称:"+ List.class.getClassLoader());

        ClassLoader c1 = ClassLoaderTest.class.getClassLoader();

        while (c1 != null){
            System.out.print(c1.getClass().getName()+ "-->");
            c1 = c1.getParent();
        }
        showMsg(c1);
    }

    private static void showMsg(Object msg){
        System.out.println(msg);
    }
}

输出接口为

1.ClassLoaderTest 类的加载器的名称:sun.misc.Launcher$AppClassLoader
2.System 类的加载器的名称:null
3.List 类的加载器的名称:null
4.sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null

说明一下:
1.ClassloaderTest 类是用户自定义的类,位于CLASSPATH ,由系统应用程序加载器加载。
2.System和List 类都是属于java类的核心类,由祖先类启动类加载器加载,而启动类加载器是在JVM内部通过c/c++实现的。并不是Java实现的,自然就不能继承classLoader类,自然就不能输出类名
3.箭头代表加载流程,层级委托,从祖先开始直到系统应用程序加载器才被加载。

测试一下,将上面的Java代码打包成jar包,放到……jdk/jre/lib/ext下面
如图

然后再执行代码,运行结果是:

ClassLoaderTest 类的加载器的名称:sun.misc.Launcher$ExtClassLoader
System 类的加载器的名称:null
List 类的加载器的名称:null
sun.misc.Launcher$ExtClassLoader-->null

从上面可以看出,没有通过系统类加载器来加载,因为类的jar包放到ExtClassLoader的加载目录下,所以当在根目录加载找到不到后,在ExtClassLoader目录处找到所以忽略的AppClassLoader阶段。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章