深入理解java虚拟机(三)

类加载机制

Posted by Kinsomy on April 30, 2019

类加载机制

类加载过程的生命周分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。

验证、准备、解析三个阶段成为连接。

1. 类加载的时机

1.1 加载

虚拟机规范中没有明确规定加载时机,由不同的虚拟机实现自行决定。

1.2 初始化

下面5种情况必须立即初始化:

1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

摘录来自: 周志明. “深入理解Java虚拟机:JVM高级特性与最佳实践。” iBooks.

下面几种情况不会触发类的初始化:

  • 通过子类引用父类的静态变量
  • 定义类的数组,如SampleObj[] array = new SampleObj[10]
  • 常量在编译阶段会存入调用类的常量池中,没有直接引用定义常量的类

2. 类加载的过程

2.1 加载

加载阶段完成三件事情:

  • 通过一个类的全路径限定名获取定义该类的二进制字节流。
  • 将获得的字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

2.2 验证

验证的作用是为了确保Class文件的字节流包含的内容符合虚拟机的要求,没有存在危害系统的风险,验证分四个主要部分:

2.2.1 文件格式验证

主要是对Class文件头部字节的信息进行验证,确定是否符合虚拟机的要求,包括是否以0xCAFEBABE开头,版本号等。

2.2.2 元数据验证

验证代码是否符合Java语言的规范。

2.2.3 字节码验证

对类的字节码校验,验证是否符合程序的语义。

2.2.4 符号引用验证

验证引用是否可以被正确访问,验证是否存在描述的符号以及符号名字、位置的正确性等。

2.3 准备

准备阶段是为类变量分配内存并赋初始值的阶段,这个阶段仅仅为类静态变量分配内存,也就是说分配的内存都是在方法区上操作的。如果是静态常量的话,不光为其在方法区分配内存,还要为这个变量赋值。

public static int i1 = 110;//准备阶段赋值0
public static final int i2 = 110;//准备阶段赋值110

2.4 解析

将常量池中的符号引用替换为直接引用。

2.5 初始化

这个阶段根据特定代码初始化变量和资源,执行类构造器<clinit>()方法

3 类加载器

认定两个类相等必须是由同一个类加载器加载出来的并且全限定名相同的两个类。

3.1 双亲委派模型

启动类加载器

启动类加载器(Bootstrap ClassLoader)是C++实现的,属于虚拟机的一部分,负责将<JAVA_HOME>\lib中的类库加载到虚拟机内存中。

扩展类加载器

扩展类加载器(Ext ClassLoader)负责将<JAVA_HOME>\lib\ext目录中的类库加载到内存中。

应用程序类加载器

应用程序类加载器负责加载使用者指定的类库,默认是系统类加载器,通过ClassLoader.getSystemClassLoader()获得。

上面的图就是类加载器的双亲委派模型,双亲委派模型的意思就是当接收到类加载请求的时候,它会先向它的父类加载器报告,委托给父类加载器加载,然后父类加载器再层层向上委托,直到达到最顶层的启动类加载器,然后启动类加载器没法实现类加载的时候,再层层向下委派,最终找到合适的类加载器去加载。

双亲委派模型的好处就是解决了上面提到的可能因为不是同一个类加载器创建了两个相同的全限定性名的类导致两个类实际不相等的情况,会将同一个全限定性名的类最终全部由同一个类加载器加载出来,提高了程序的稳定性。

双亲委派模型的实现在java.lang.ClassLoader#loadClass(String, boolean)中:

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
                }

                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;
        }
    }

参考资料

  • 深入理解Java虚拟机:JVM高级特性与最佳实践