《深入理解Java虚拟机》:类加载和初始化(二)

《深入理解Java虚拟机》:类加载和初始化(二)

在去年看《深入理解Java虚拟机》的时候,写过一篇关于类加载和初始化的博客,最近又在看这一块的知识,发现还是有很多东西没有理解好。借助于网上的一些博客,学习了一点新的知识,并整理如下。

1、类加载过程

类加载过程由如下几个阶段构成:装载、链接、初始化。其中链接包括:验证、准备和解析三个阶段。

1、装载:查找并加载类的二进制数据

2、链接:

  • 验证:确保被加载类的正确性
  • 准备:为类的静态属性分配内存并初始化为默认值。
  • 解析:把类中的符号引用转换为直接引用

3、初始化:为类的静态变量赋予正确的初始值。

那为什么还要有验证这一步骤呢?

首先如果由编译器生成的class文件,它肯定是符合JVM字节码格式的,但是万一有高手自己写一个class文件,让JVM加载并运行,用于恶意用途,就不妙了,因此这个class文件要先过验证这一关,不符合的话不会让它继续执行的,也是为了安全考虑吧。

准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的.

如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

2.类的初始化

类什么时候才被初始化:

1)创建类的实例,也就是new一个对象

2)访问某个类或接口的静态变量,或者对该静态变量赋值

3)调用类的静态方法

4)反射(Class.forName(“com.lyj.load”))

5)初始化一个类的子类(会首先初始化子类的父类),注:子类初始化问题:满足主动调用,即父类访问子类中的静态变量、方法,子类才会初始化;否则仅父类初始化。具体看这篇博客:http://blog.csdn.net/u010412719/article/details/47059439

6)JVM启动时标明的启动类,即文件名和类名相同的那个类

只有这6中情况才会导致类的类的初始化。

类的初始化步骤

1)如果这个类还没有被加载和链接,那先进行加载和链接

2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)

3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

3.加载器

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:(七牛云存储好像出bug了,访问不了,因此就没有图片)

Bootstrap classLoader:加载核心库文件

Extension classLoader:加载扩展库文件

App classLoader:加载classpath路径下的jar包,我们写的java类,一般都是由它加载,除非你自己制定个人的类加载器。

loadClass方法的源码如下:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    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 {
                    /* 如果有父类加载器,则交给父类加载器去加载。 如果没有父类加载器,则说明此加载器为extension classLoader,直接交给Bootstrap classLoader加载器去加载。 */
                    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
                }
                /* 如果父类加载器没有加载成功,则自己加载。 */
                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;
        }
    }

源码思想思路如下:

假设加载类A,加载过程如下

1、首先检查当前加载器是否加载了类 A,如果没有,则进行 2,否则进行 7

2、检查当前加载器是否有父加载器,如果有,则进行 3, 否则说明当前加载器为extension classLoader,因此进行 4.

3、当类A委托给父加载器进行加载(父类加载器加载的过程和这个过程类似,也是先检查类A是否已经加载,
如果没有,检查是否有父类加载器,如果有,则进一步委托给父加载器),如果加载成功,则 进行 7,否则进行 5.

4、将类A委托给Bootstrap classLoader进行加载,如果加载成功,则进行 7,否则进行 5.

5、检查类A是否已经加载成功,如果没有,则进行 6,否则进行 7.

6、当前加载前将自己亲自加载类 A .

7、退出

总结:加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类,即从Bootstrap classLoader到custom classLoader逐层尝试加载此类。

类的卸载

关于类的卸载可以看这篇博客,讲解的比较好。http://www.cnblogs.com/mengdd/p/3594608.html

参考博客

1、http://blog.csdn.net/gjanyanlig/article/details/6818655此博客中关于类加载过程讲解的比较清晰。

2、http://www.cnblogs.com/smyhvae/p/4810168.html

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/u010412719/article/details/52279578
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞