类加载过程(一些细节+过程)

类加载阶段

1. 加载

  • 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表
    • _itable 接口方法表
  • 如果这个类还有父类没有加载,先加载父类
  • 加载和链接可能是交替运行的

2. 链接

  • 第一阶段:验证

    验证类是否符合 JVM规范,安全性检查

  • 第二阶段:准备阶段,为static变量分配空间,设置默认值
    • static变量在JDK7之前存储于instanceKlass末尾,在JDK7开始,存储于_java_mirror末尾(instanceKlass位于方法区,_java_mirror位于堆中。1.8之前方法区位于永久代,之后将方法区置于元空间)

    • static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成

    • 如果static变量是final的基本类型或者字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成

    • 如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成

      示例:

          static int a;
          static int b = 10;
          static final int c = 20;
          static final String d = "HelloWorld";
          static final LocalDate e = LocalDate.now();
      

      编译之后的结果:

      static int a;
          descriptor: I
          flags: ACC_STATIC
      
        static int b;
          descriptor: I
          flags: ACC_STATIC
      
        static final int c;
          descriptor: I
          flags: ACC_STATIC, ACC_FINAL
          ConstantValue: int 20
      
        static final java.lang.String d;
          descriptor: Ljava/lang/String;
          flags: ACC_STATIC, ACC_FINAL
          ConstantValue: String HelloWorld
      
        static final java.time.LocalDate e;
          descriptor: Ljava/time/LocalDate;
          flags: ACC_STATIC, ACC_FINAL
      
      

      能够看到字母c d在编译完成后就已经有值了。

    • 第三阶段: 解析

      将常量池中的符号引用解析为直接引用

      public class Load2 {
      public static void main(String[] args) throws ClassNotFoundException,
      IOException {
          ClassLoader classloader = Load2.class.getClassLoader();
          // loadClass 方法不会导致类的解析和初始化
          Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");
          // new C();
          System.in.read();
      }
      }
      class C {
      D d = new D();
      }
      class D {
      }
      
      

      这里因为加载的时候,使用的懒加载,所以此时加载类C不会加载类D,在常量池中,涉及到类D的信息,会告诉类D是一个未经解析的类。但是使用new对象时,就不一样了。

    3. 初始化

    初始化调用()v方法,虚拟机会保证这个类的构造方法线程安全。

    发生时机

    概括得说,类初始化是懒惰的。

    • main方法所在的类,总会被首先初始化
    • 首次访问这个类的静态变量或静态方法时
    • 子类初始化,如果父类还没初始化,会引发
    • 子类访问父类的静态变量,只会触发父类的初始化
    • Class.forName
    • new会导致初始化

    不会导致类初始化的情况

    • 访问类的static final静态常量(基本类型和字符串)不会触发初始化
    • 类对象.class不会触发初始化
    • 创建该类的数组不会触发初始化
    • 使用Class.forName方法,第二个参数设为false的情况(默认为true)

    示例:

    public class Load3 {
        static {
        	  System.out.println("main init");
        }
    public static void main(String[] args) throws ClassNotFoundException {
              // 1. 静态常量(基本类型和字符串)不会触发初始化
              System.out.println(B.b);
              // 2. 类对象.class 不会触发初始化
              System.out.println(B.class);
              // 3. 创建该类的数组不会触发初始化
              System.out.println(new B[0]);
              // 4. 不会初始化类 B,但会加载 B、A
              ClassLoader cl = Thread.currentThread().getContextClassLoader();
              cl.loadClass("cn.itcast.jvm.t3.B");
              // 5. 不会初始化类 B,但会加载 B、A
              ClassLoader c2 = Thread.currentThread().getContextClassLoader();
              Class.forName("cn.itcast.jvm.t3.B", false, c2);
              // 1. 首次访问这个类的静态变量或静态方法时
              System.out.println(A.a);
              // 2. 子类初始化,如果父类还没初始化,会引发
              System.out.println(B.c);
              // 3. 子类访问父类静态变量,只触发父类初始化
              System.out.println(B.a);
              // 4. 会初始化类 B,并先初始化类 A
              Class.forName("cn.itcast.jvm.t3.B");
    	}
    }
    
    class A {
        static int a = 0;
        static {
        	System.out.println("a init");
        }
    }
    class B extends A {
        final static double b = 5.0;
        static boolean c = false;
        static {
        	System.out.println("b init");
        }
    }
    
    
    

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值