java类加载机制

来源:互联网 发布:网络自媒体 编辑:程序博客网 时间:2024/06/05 01:51

类加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

过程如下图:
这里写图片描述

类的生命周期

类的生命周期
它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。类加载只包括前面五个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。
加载,验证,准备,初始化这几个阶段是按照顺序开始的,解析则不一定,因为当一个方法动态绑定时,那么该方法是在运行阶段完成的解析(也就是我们所说的多态)。同时这几个阶段只是按照顺序开始,并不意味着当一个阶段完成之后才能执行另一个阶段,有时可能当一个阶段还在执行之中就下一个阶段就开始。比如在加载的过程中往往伴随着验证的过程。

加载

查找并加载.class文件中的二进制数据。
加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

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

虚拟机并没有指定.class文件的数据来源所以可以从下面几个方面获取:
1、 通过网络加载.class文件
2、从zip,jar等归档文件中加载.class文件
3、从专有数据库中提取.class文件
4、从磁盘中获取
5、运行时生成,使用最多的就是动态代理技术
6、有其他文件生成,常见的就是jsp技术

验证

验证主要是验证加载过来的二进制数据符合虚拟机的要求,并且不会损害虚拟机自身的安全。
验证主要是从以下几个方面进行验证:
1、文件格式的验证
验证字节流是否符合Class文件的规范,同时还可以被当前版本的虚拟机所处理
2、元数据验证
对加载到方法区的类的元数据进行语义分析。
3、字节码验证
通过对数据流和控制流进行分析,确保程序语义合法,合乎逻辑。
4、符号引用验证
对常量池中的符号引用进行验证。这个阶段的验证发生在解析的时候。
注意:
对于虚拟机的类加载机制来说,验证阶段是一个非常重要的、但不一定是必要(因为对程序运行时没有影响)的阶段。如果所运行的全部代码(包括自己编写的以及第三方的包中)已经被反复使用和验证过,可以使用-Xverify:none参数来关闭大部分类的验证,以缩短虚拟机类加载的时间。

准备

为类变量分配内存并赋予初始值。
需要注意的是这里指的变量是类变量(被static修饰的变量),类变量是存储与方法区中,而不是实例变量。实例变量将会伴随着对象的初始化在堆中分配内存。
其次,这里值的赋予的初始值是零值。
不妨,我们来看看下面这个例子

public static int value = 123;

在准备阶段被赋予的值是0而不是123,被赋予123是在初始化阶段之后。
但是也会存在特殊的情况,某一个类变量被final关键字修饰,那么这个字段的属性表中就会存在ConstantValue属性,那么准备阶段就会赋予ConstantValue指定的值,比如

public static final int value = 123

那么准备阶段过后value的值是123。

解析

虚拟机将常量池中的符号引用转换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。这一个阶段比较的特殊它可以发生在类加载阶段,也可以发生运行时。类加载阶段发生的解析我们称作静态解析,在类加载阶段发生的解析叫做动态绑定。

初始化

根据程序员所写的代码来初始化类变量和其他静态资源。
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下几种。

(1) 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
(2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
(3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
(4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

需要注意的是,如果在多线程环境下对类进行初始化,那么其中一个线程对类进行初始化的时其他的线程会进入等待状态。所以类的初始化阶段是线程安全的。

类加载器

类加载器通过类的全限定名将.Class文件中的数据加载到了方法区之中。
可能有人就有知道我们平时写代码的时候类加载,通过下面这个例子来看,平时我们写代码的时候类加载器的层次结构。

public class TestClassLoader {    public static void main(String[] args) {        ClassLoader loader = Thread.currentThread().getContextClassLoader();        System.out.println(loader);        System.out.println(loader.getParent());        System.out.println(loader.getParent().getParent());    }}输出结果:sun.misc.Launcher$AppClassLoadersun.misc.Launcher$ExtClassLoadernull

AppClassLoader加载我们所写的程序的.class文件(classpath环境下的.class文件),ExtClassLoader是AppClassLoader的父类加载器,但是ExtClassLoader的父类是null,原因是启动类加载器(BootStrapClassLoader)是用c++实现的找不到一个确定的返回父Loader的方式,于是就返回null。同时需要注意的是,这里的父子关系不使用继承关系实现的,而是使用组合。
这几种类加载器的层次关系如下图所示:
这里写图片描述
(1) Bootstrap ClassLoader : 将存放于\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
(2) Extension ClassLoader : 将\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
(3) Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。
(4)用户自定义类加载器:继承java.lang.ClassLoader可以加载用户指定位置的.class文件。
注意,除了BootStrapClassLoader是使用C++实现的之外,其余的类加载器都继承了java.lang.ClassLoader类。

双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
好处:
-保证不同的类加载环境下基类的唯一性,例如,我们写一个java.lang.String类放入classpath环境下,将无法被加载。
-保证Java程序安全稳定运行。
ClassLoader源码分析

public Class<?> loadClass(String name)throws ClassNotFoundException {     return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException{    synchronized (getClassLoadingLock(name)) {        //首先检查该类是否被加载        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异常                // 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;    }}

自定义类加载器

public class MyClassLoader extends ClassLoader {    private String baseDir;    public MyClassLoader(String baseDir) {        this.baseDir = baseDir;    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        String fileName = baseDir + File.separatorChar                + name.replace('.', File.separatorChar) + ".class";        byte[] bytes = readBytesFromFile(fileName);        return this.defineClass(name, bytes, 0, bytes.length);    }    private byte[] readBytesFromFile(String filename){        InputStream in = null;        ByteArrayOutputStream bos = null;        try {            in = new FileInputStream(filename);            bos = new ByteArrayOutputStream();            byte[] buffer = new byte[1024];            int len = 0;            while ((len = in.read(buffer)) != -1){                bos.write(buffer,0,len);            }            return bos.toByteArray();        } catch (Exception e) {            e.printStackTrace();        }finally {            if(bos != null){                try {                    bos.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if(in != null){                try {                    in.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return null;    }    public static void main(String[] args) throws ClassNotFoundException {        MyClassLoader loader = new MyClassLoader("G:\\code");                          System.out.println(loader.findClass("com.fjh.Test").getName());    }}输出结果:com.fjh.Test

注意:
1、最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
2、不能把com.fjh.Test类放在ClassPath环境下,否则就会有AppClassLoader来加载该类,就轮不到我们自定义的类加载器来加载该类了。

3 0
原创粉丝点击