虚拟机加载机制(深入理解java虚拟机笔记)

目录

加载

验证

文件格式验证

 元数据验证

方法的字节码验证

符号引用验证

准备

解析

初始化

类加载器

双亲委派

双亲委派的创新

线程上下文加载器(SPI类加载模式)

OSGI

你的打赏是我奋笔疾书的动力!​


       类从被加载到虚拟机所管辖的内存中,再到卸载出内存,包括:

加载开始于需要加载该类的地方。

         而对类的初始化“有且仅有”5中情况:

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

         2. 使用java.lang.reflect包的方法对类进行反射调用时候。

         3. 当初始化一个类时,发现其父类没有进行初始化,则需先触发其父类的初始化。

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

         5. 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_putStatic、REF_getStatic、REF_invokeStatic的方法句柄,并且此方法句柄所对应的类没有进行初始化时,需要初始化该类。

         至于初始化的行为见后面小节,主要就是执行按java文件的书写顺序合并了的静态字段的赋值操作和代码块操作。

         以上5种称为对类的主动引用,除以上5种情况外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的例子网上查找。

加载

         3件事情:

 

验证

文件格式验证

 元数据验证

 

方法的字节码验证

         ……

符号引用验证

准备

         为类变量在方法区中分配内存,并赋初始值。

         Public static int value = 123;//准备阶段赋值为0,初始化阶段赋值为123

         Public static final int value = 123;//编译期在文件中已经有包含123信息的constantvalue属性,所以在准备阶段直接赋值123,初始化阶段无行为。

解析

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

         符号引用类型有:CONSTANT_Class_info 、CONSTANT_Fieldref_info、CONSTANT_Method_info、CONSTANT_InterfaceMethodRef_info、CONSTANT_MethodType_info类型、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info等分别是类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符(符号引用)等。

         直接引用是:指向目标的指针、相对便宜量、可以间接定位到目标的句柄。

         anewarray、 checkcast、 getfield、 getstatic、 instanceof、nvokedynamic、 invokeinterface、 invokespecial、 invokestatic、 invokevirtual、ldc、 ldc_w、 multianewarray、 new、 putfield 和 putstatic上述任何一条字节码指令都需要对它的符号引用的进行解析。

         类或接口符号引用的解析 略

         字段符号引用的解析 略

         类方法符号引用的解析 略

         接口方法符号引用的解析 略

         其他类型符号引用的解析,例如:CONSTANT_String_info,维护在虚拟机的一张(intern)内部列表,该列表里存放字符串对象的引用,解析就是把CONSTANT_String_info替换为虚拟机中字符串对象的引用。String类的intern方法返回维护列表的引用值。

初始化

         此段才真正开始执行字节码……虚拟机生成的<clinit>方法,而非类实例方法<init>,该方法合并了类变量的赋值操作和静态代码块操作,他就是初始化要执行的字节码。虚拟机保证父类的<clinit>方法先执行,因此第一个被执行<clinit>方法的类是java.lang.Object。

         如果类中没有类变量赋值操作,也没有静态代码块,则不生成<clinit>方法;

         接口中不能有静态代码块,可又有接口(类)变量,因此接口有<clinit>方法,只有当接口中的变量被使用时,接口才被初始化(<clinit>方法被调用执行),实现类若没有使用接口的接口变量,则接口不需用初始化。

         <clinit>方法指挥初始化一次,所以会同步加锁。

 示例

package org.fenixsoft.clazz;

import java.util.Collections;

/**
 * 常量池中CONSTANT_Fieldref_info、CONSTANT_Method_info、CONSTANT_InterfaceMethodRef_info等类型的常量
 * 都会有一项Class_index属性,它指向一个CONSTANT_Class_info类型的常量,该常量描述了类全限定名
 * 还有CONSTANT_MethodType_info类型、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info
 * 这3种和jdk1.7新增动态语言特性相关
 */
public class TestClass<Tc> {//Class级的属性列表里有签名属性
    //准备阶段赋值为0,初始化阶段在<clinit>方法中赋值123
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    public static int value = 123;
    //编译期赋值123在Class文件中
    //无CONSTANT_Fieldref_info类型常量,值已经编译在value1字段表的constant属性里
    public final static int value1 = 123;
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    private int m;
    //在Class文件中,对应的字段表含有签名属性
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    private Tc t;

    //以下测试字段的解析
    //引用类型如果在本类中没有使用(赋值语句等),那么就不会在常量池里编译出
    // CONSTANT_Fieldref_info和CONSTANT_Class_info信息
    private TestInterfaceRef testInterfaceRef;
    //有使用(赋值语句等),在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    private TestInterfaceRef testInterfaceRef1 = new TestInterfaceImplRef();
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    //"一二三"和"一二三四"字符串对象的引用被维护在虚拟机的一张(intern)内部列表里
    private String testStringRef = "一二三";
    private String testStringRef2 = "一二三四";
    //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
    private String testStringRef1 = new String("abc");


    //编译后code里都是去泛型信息的,但会在方法表、相关字段表、
    // Class级的属性列表里增加一项Signature属性,
    // Signature属性记录有泛型信息
    public <Pm> int inc(Pm pt,Tc tc) {
        //已经去了泛型信息
        this.t = tc;
        //在本类中有了使用(赋值语句等),在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
        //CONSTANT_Fieldref_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
        //1.    new #2 <org/fenixsoft/clazz/TestInterfaceImplRef>
        //#2 : CONSTANT_Class_info : Class name : cp_info_#58 <org/fenixsoft/clazz/TestInterfaceImplRef>
        //2.    invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>>
        //#3 : CONSTANT_MethodRef_info : Class name : cp_info_#2 <org/fenixsoft/clazz/TestInterfaceRef>
//        this.testInterfaceRef = new TestInterfaceImplRef();

        //引用其他类实例的字段并赋值,
        /**
         6 new #2 <org/fenixsoft/clazz/TestInterfaceImplRef> #2 : 同上
         9 dup
         10 invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>> #3 : 同上
         13 getfield #12 <org/fenixsoft/clazz/TestInterfaceImplRef.ref> 生成一个要引用的CONSTANT_FiledRef_info#12
         #12 : CONSTANT_FiledRef_info : Class name : cp_info_#58 <org/fenixsoft/clazz/TestInterfaceImplRef>
         16 putfield #13 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
         #13 : CONSTANT_FiledRef_info : Class name : cp_info_#17 <org/fenixsoft/clazz/TestClass>
         */
        this.testInterfaceRef = new TestInterfaceImplRef().ref;


       //在本类中有了使用testInterfaceRef,在常量池里就会有CONSTANT_Fieldref_info和CONSTANT_Class_info信息
        //还会出现CONSTANT_InterfaceMethodRef_info(含有ClassIndex)
        // invokeinterface #13 <org/fenixsoft/clazz/TestInterfaceRef.test>
        //#13 : CONSTANT_InterfaceMethodRef_info : Class name : cp_info_#68 <org/fenixsoft/clazz/TestInterfaceRef>
        testInterfaceRef.test();


        //此代码会在常量池里出现相应的CONSTANT_Method_info和CONSTANT_Class_info信息
        // invokestatic #13 <java/util/Collections.sort>
        //#13 : CONSTANT_Method_info : Class name : cp_info_#70 <java/utils/Collections>
        Collections.sort(null);

        //下面测试Class文件中的异常处理表
        int x;
        try {
            x = 1;
            return x;
        } catch (Exception e) {
            x = 2;
            return x;
        } finally {
            x = 3;
        }
    }

}

inc方法的字节码有:

 0 aload_0
 1 aload_2
 2 putfield #13 <org/fenixsoft/clazz/TestClass.t>
 5 aload_0
 6 new #2 <org/fenixsoft/clazz/TestInterfaceImplRef>
 9 dup
10 invokespecial #3 <org/fenixsoft/clazz/TestInterfaceImplRef.<init>>
13 getfield #14 <org/fenixsoft/clazz/TestInterfaceImplRef.ref>
16 putfield #15 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
19 aload_0
20 getfield #15 <org/fenixsoft/clazz/TestClass.testInterfaceRef>
23 invokeinterface #16 <org/fenixsoft/clazz/TestInterfaceRef.test> count 1
28 aconst_null
29 invokestatic #17 <java/util/Collections.sort>
32 iconst_1
33 istore_3
34 iload_3
35 istore 4
37 iconst_3
38 istore_3
39 iload 4
41 ireturn
42 astore 4
44 iconst_2
45 istore_3
46 iload_3
47 istore 5
49 iconst_3
50 istore_3
51 iload 5
53 ireturn
54 astore 6
56 iconst_3
57 istore_3
58 aload 6
60 athrow

类加载器

         判断一个类和另一个类在虚拟机中是否相等,取决于该类是否是同一个加载器加载,是否来自于同一份Class文件。这里的相等包括了Class对象的equals、isAssignableFrom、isInstance方法,也包括instanceof关键字等做对象所属关系判定情况。

双亲委派

双亲委派的创新

线程上下文加载器(SPI类加载模式)

 正统的classloder:

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

         SPI设计规则被提出的需求是:系统已经规范好了各种服务接口的内容,各个第三方的服务提供商根据系统的接口实现自己的服务类,然后我们选择一些服务类向系统注册,并只是使用系统的接口来享受服务。

         那么我们享受服务的时候,第三方的服务类一般情况下应该处于classpath下,应该由Appclassloader加载,系统的类由BootStrapLoader加载或者扩展类加载器加载总之是Appclassloader的父加载器,这时候系统的类要使用第三方服务类先要加载第三方的服务类,问题是如何加载?

         答案应该是系统的类使用Class.forName(class.getName(), true or false, classLoader);并指定classLoader为Appclassloader,没错,也就是线程上下文类加载器,因为线程上下文加载器在JVM初始化时被设置成了Appclassloader。

         启动、扩展、应用、自定义类加载器的关系已经铁打,然而不要忘了,线程上下文类加载器是对他们其中之一的引用,个人感觉这是一个让人揪心的设计。

OSGI

         OSGI的类加载器不再是双亲委托机制的树状结构,而是复杂的网状结构。需要深入理解请参考:《深入理解OSGI:Equinox原理、应用与最佳实践》

你的打赏是我奋笔疾书的动力!

支付宝打赏:

微信打赏:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值